diff --git a/AGENTS_INSTALL.md b/AGENTS_INSTALL.md index c4a80069fd7..0bab328853d 100644 --- a/AGENTS_INSTALL.md +++ b/AGENTS_INSTALL.md @@ -92,7 +92,7 @@ A host that has both a Task Scheduler entry and a WinSW service is in a conflict ### The CLI -`npm install -g @bitkyc08/opencodex` puts `ocx` on the PATH from npm's global prefix, and the +`npm install -g --allow-scripts=bun @bitkyc08/opencodex` puts `ocx` on the PATH from npm's global prefix, and the Bun runtime it needs is bundled inside that package. There is no separate runtime to install and no WSL layer on Windows. diff --git a/README.md b/README.md index db98afcaf0a..263079fb129 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ and connect OpenCode, Pi, Qoder, WorkBuddy, Kilo, Cline, T3 Code and more throug

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -94,7 +94,7 @@ providers with Codex, Claude Code, Claude Desktop, Grok Build and the tools list ### Personal install (CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18+; the Bun runtime is bundled automatically +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18+; the Bun runtime is bundled automatically ocx start # proxy + dashboard on localhost:10100 ``` @@ -253,7 +253,7 @@ they reach the npm package. For agents ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # or `ocx service` ocx init # interactive setup: writes ~/.opencodex/config.json and wires Codex ``` diff --git a/bin/ocx.mjs b/bin/ocx.mjs index 72eff87dfcf..ef5b11ad33d 100755 --- a/bin/ocx.mjs +++ b/bin/ocx.mjs @@ -331,7 +331,7 @@ async function runPackageManagerSelfUpdate(manager, child) { const latestInvocation = managerInvocation(["view", `${PKG}@${selector}`, "version"]); const installArgs = manager === "pnpm" ? ["add", "-g", "--allow-build=bun", `${PKG}@${selector}`] - : ["install", "-g", `${PKG}@${selector}`]; + : ["install", "-g", "--allow-scripts=bun", `${PKG}@${selector}`]; const installInvocation = managerInvocation(installArgs); if (!latestInvocation || !installInvocation) { console.error(`opencodex: could not resolve ${manager} from a trusted absolute PATH entry; aborting before stopping the proxy.`); diff --git a/docs-site/src/components/Landing.astro b/docs-site/src/components/Landing.astro index 99a3f92abb8..c7f68222199 100644 --- a/docs-site/src/components/Landing.astro +++ b/docs-site/src/components/Landing.astro @@ -165,14 +165,14 @@ const providers: { label: string; path?: string }[] = [ ]; const quickstartMini = [ - { cmd: 'npm install -g @bitkyc08/opencodex', note: t('Install', '설치', '安装', 'Установка', 'インストール', '安裝', 'Kurulum') }, + { cmd: 'npm install -g --allow-scripts=bun @bitkyc08/opencodex', note: t('Install', '설치', '安装', 'Установка', 'インストール', '安裝', 'Kurulum') }, { cmd: 'ocx init', note: t('Add a provider and connect Codex', '프로바이더 추가 후 Codex 연결', '添加提供商并连接 Codex', 'Добавьте провайдера и подключите Codex', 'プロバイダーを追加して Codex を接続', '新增供應商並連接 Codex', 'Sağlayıcı ekleyip Codex’i bağlayın') }, { cmd: 'ocx start', note: t('Start the proxy', '프록시 시작', '启动代理', 'Запуск прокси', 'プロキシを起動', '啟動代理', "Proxy'yi başlatın") }, { cmd: 'ocx claude', note: t('In another terminal: launch Claude Code through ocx', '다른 터미널에서: ocx를 거쳐 Claude Code 실행', '在另一个终端中:通过 ocx 启动 Claude Code', 'В другом терминале: запуск Claude Code через ocx', '別のターミナルで: ocx 経由で Claude Code を起動', '在另一個終端機中:透過 ocx 啟動 Claude Code', 'Başka bir terminalde: Claude Code’u ocx üzerinden başlatın') }, ]; const quickstart = [ - { cmd: 'npm install -g @bitkyc08/opencodex', note: t('Install', '설치', '安装', 'Установка', 'インストール', '安裝', 'Kurulum') }, + { cmd: 'npm install -g --allow-scripts=bun @bitkyc08/opencodex', note: t('Install', '설치', '安装', 'Установка', 'インストール', '安裝', 'Kurulum') }, { cmd: 'ocx init', note: t('Add a provider and connect Codex', '프로바이더 추가 후 Codex 연결', '添加提供商并连接 Codex', 'Добавьте провайдера и подключите Codex', 'プロバイダーを追加して Codex を接続', '新增供應商並連接 Codex', 'Sağlayıcı ekleyip Codex’i bağlayın') }, { cmd: 'ocx start', note: t('Start the proxy', '프록시 시작', '启动代理', 'Запуск прокси', 'プロキシを起動', '啟動代理', "Proxy'yi başlatın") }, { cmd: 'codex "Write a hello world in Rust"', note: t('Codex now routes through ocx', 'Codex가 ocx를 경유', 'Codex 现在经由 ocx 路由', 'Codex теперь работает через ocx', 'Codex は ocx 経由に', 'Codex 現在經由 ocx 路由', 'Codex artık ocx üzerinden yönlendirilir') }, @@ -344,7 +344,7 @@ const docsMap = [

{t('Prefer the terminal?', '터미널이 더 편하다면', '更喜欢终端?', 'Предпочитаете терминал?', 'ターミナル派ですか?', '偏好終端機?', 'Terminali mi tercih edersiniz?')} - npm install -g @bitkyc08/opencodex + npm install -g --allow-scripts=bun @bitkyc08/opencodex {t('Installation guide', '설치 가이드', '安装指南', 'Руководство по установке', 'インストールガイド', '安裝指南', 'Kurulum kılavuzu')} {t('All releases and checksums', '전체 릴리스와 체크섬', '所有版本与校验和', 'Все релизы и контрольные суммы', 'すべてのリリースとチェックサム', '所有版本與校驗碼', 'Tüm sürümler ve sağlama toplamları')} {t('Desktop app guide', '데스크톱 앱 가이드', '桌面应用指南', 'Руководство по приложению', 'デスクトップアプリガイド', '桌面應用程式指南', 'Masaüstü uygulaması kılavuzu')} diff --git a/docs-site/src/content/docs/fr/getting-started/for-agents.md b/docs-site/src/content/docs/fr/getting-started/for-agents.md index 3e962903062..671d1d01e02 100644 --- a/docs-site/src/content/docs/fr/getting-started/for-agents.md +++ b/docs-site/src/content/docs/fr/getting-started/for-agents.md @@ -13,7 +13,7 @@ disponible pour une configuration interactive ; consultez le [tableau de bord w Installez le package publié et confirmez que `ocx` est sur `PATH` : ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/fr/getting-started/installation.md b/docs-site/src/content/docs/fr/getting-started/installation.md index 65fb3d91133..456907e4e9e 100644 --- a/docs-site/src/content/docs/fr/getting-started/installation.md +++ b/docs-site/src/content/docs/fr/getting-started/installation.md @@ -19,7 +19,7 @@ ChatGPT lorsqu’un modèle routé en a besoin. ## Installer ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` :::note[npm a-t-il bloqué le script postinstall de bun ?] @@ -52,7 +52,7 @@ les entrées du catalogue n’accordent aucun accès par elles-mêmes. N’utili tester des versions opencodex non encore publiées : ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/fr/troubleshooting/windows-memory.md b/docs-site/src/content/docs/fr/troubleshooting/windows-memory.md index cae4dce2521..b0d3f4476db 100644 --- a/docs-site/src/content/docs/fr/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/fr/troubleshooting/windows-memory.md @@ -44,4 +44,4 @@ Si vous essayez l’une de ces solutions avec une charge Windows réelle, veuill L’installation et la réparation du service Windows ainsi que l’installation et l’actualisation du shim Codex vérifient que l’exécutable sélectionné peut créer puis supprimer un répertoire dans la racine de configuration OpenCodex. Ce contrôle de politique d’écriture ne corrige pas la mémoire. Un échec refuse l’opération avant tout arrêt, téléchargement, préparation ou écriture de jeton ou de lanceur. La réparation automatique du shim est différée avec des instructions et le démarrage continue. Les shims sains ou désactivés ne sont pas testés. Aucun autre runtime n’est recherché. Une première installation du planificateur enregistre sa tâche et revendique la racine de configuration avant ce contrôle ; en cas de refus, cet enregistrement est annulé et la racine revendiquée est conservée pour une nouvelle tentative. -> Le runtime Bun sélectionné ne peut pas créer puis supprimer un répertoire de configuration. La politique d’application Windows peut interdire les écritures depuis cet emplacement. Avant de lancer `ocx`, définissez `OPENCODEX_BUN_PATH` vers un exécutable Bun de confiance autorisé par votre politique, ou réinstallez avec `npm install -g @bitkyc08/opencodex`, puis réessayez. Aucun autre runtime n’a été sélectionné. +> Le runtime Bun sélectionné ne peut pas créer puis supprimer un répertoire de configuration. La politique d’application Windows peut interdire les écritures depuis cet emplacement. Avant de lancer `ocx`, définissez `OPENCODEX_BUN_PATH` vers un exécutable Bun de confiance autorisé par votre politique, ou réinstallez avec `npm install -g --allow-scripts=bun @bitkyc08/opencodex`, puis réessayez. Aucun autre runtime n’a été sélectionné. diff --git a/docs-site/src/content/docs/getting-started/for-agents.md b/docs-site/src/content/docs/getting-started/for-agents.md index b02c39812a5..9bba3f1bfc1 100644 --- a/docs-site/src/content/docs/getting-started/for-agents.md +++ b/docs-site/src/content/docs/getting-started/for-agents.md @@ -13,7 +13,7 @@ available for interactive configuration; see [Web Dashboard](/guides/web-dashboa Install the published package and confirm that `ocx` is on `PATH`: ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/getting-started/installation.md b/docs-site/src/content/docs/getting-started/installation.md index 7eab3cd9117..a2660d4c128 100644 --- a/docs-site/src/content/docs/getting-started/installation.md +++ b/docs-site/src/content/docs/getting-started/installation.md @@ -30,7 +30,7 @@ for one package invocation. That bypass does not repair the Desktop record, shim ## Install ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` With pnpm 10.4 or later: @@ -70,6 +70,12 @@ npm install -g --allow-scripts=bun @bitkyc08/opencodex # if the original install used sudo, keep using sudo: sudo npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` + +On Windows a running proxy keeps `node_modules\bun\bin` open, so a manual +`npm install -g` fails with `EBUSY`. `ocx update` stops the proxy and restarts the service itself; +for a manual reinstall, stop it first (`ocx stop`), install, then restart through the owning service +(`ocx service restart`) or `ocx start` when unmanaged. `ocx doctor` prints this sequence when it finds +a package install whose bundled runtime never unpacked. ::: Verify both command aliases are on your `PATH`: @@ -116,7 +122,7 @@ the catalog entries do not grant access by themselves. Use the preview channel o unreleased opencodex builds: ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/ja/getting-started/for-agents.md b/docs-site/src/content/docs/ja/getting-started/for-agents.md index 9eb2aa29574..db3261538f7 100644 --- a/docs-site/src/content/docs/ja/getting-started/for-agents.md +++ b/docs-site/src/content/docs/ja/getting-started/for-agents.md @@ -10,7 +10,7 @@ description: ユーザー同意の境界を越えずに、エージェント主 公開されたパッケージをインストールし、`ocx` が `PATH` 上にあることを確認します。 ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/ja/getting-started/installation.md b/docs-site/src/content/docs/ja/getting-started/installation.md index 49b50f7d37d..3ac4677d744 100644 --- a/docs-site/src/content/docs/ja/getting-started/installation.md +++ b/docs-site/src/content/docs/ja/getting-started/installation.md @@ -18,7 +18,7 @@ opencodex をインストールすると同じ実行ファイルを指す `ocx` ## インストール ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` :::note[npm が bun の postinstall をブロックした?] @@ -63,7 +63,7 @@ GPT-5.6 Sol/Terra/Luna カタログ情報がすでに含まれています。た ありません。まだ正式配布されていない opencodex ビルドを試す場合のみ preview チャネルを使ってください: ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md index 774933a39f3..3bdc80e3f68 100644 --- a/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md @@ -50,4 +50,4 @@ Windows では、#32111 クラッシュを回避するために、opencodex は Windows のサービスのインストール・修復と Codex shim のインストール・更新は、選択した実行ファイルが OpenCodex 設定ルート内でディレクトリを作成・削除できるか確認します。これはメモリ修正ではなく書き込みポリシーの確認です。失敗するとサービス停止、ダウンロード、ステージング、トークンやランチャーの書き込み前に拒否します。shim の自動修復は案内付きで延期され、起動は続行します。正常または無効な shim は検査しません。別のランタイムを自動探索しません。 初回のスケジューラーインストールでは、この確認の前にタスクを登録し設定ルートを確保します。拒否された場合は新しい登録を取り消し、確保したルートは再試行用に残します。 -> 選択した Bun ランタイムが設定ディレクトリ内でディレクトリを作成・削除できませんでした。Windows のアプリケーションポリシーがこの実行ファイルの位置からの書き込みを拒否している可能性があります。`ocx` 起動前にポリシーが許可する信頼できる Bun のパスを `OPENCODEX_BUN_PATH` に設定するか、`npm install -g @bitkyc08/opencodex` で再インストールして再試行してください。別のランタイムは選択していません。 +> 選択した Bun ランタイムが設定ディレクトリ内でディレクトリを作成・削除できませんでした。Windows のアプリケーションポリシーがこの実行ファイルの位置からの書き込みを拒否している可能性があります。`ocx` 起動前にポリシーが許可する信頼できる Bun のパスを `OPENCODEX_BUN_PATH` に設定するか、`npm install -g --allow-scripts=bun @bitkyc08/opencodex` で再インストールして再試行してください。別のランタイムは選択していません。 diff --git a/docs-site/src/content/docs/ko/getting-started/for-agents.md b/docs-site/src/content/docs/ko/getting-started/for-agents.md index 4b6a07ded65..b79ffdd7f06 100644 --- a/docs-site/src/content/docs/ko/getting-started/for-agents.md +++ b/docs-site/src/content/docs/ko/getting-started/for-agents.md @@ -10,7 +10,7 @@ description: 사용자의 동의 경계를 넘지 않으면서 에이전트가 배포된 패키지를 설치하고 `ocx`가 `PATH`에 들어 있는지 확인합니다: ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/ko/getting-started/installation.md b/docs-site/src/content/docs/ko/getting-started/installation.md index 4e2ec7dbdbe..909eb049228 100644 --- a/docs-site/src/content/docs/ko/getting-started/installation.md +++ b/docs-site/src/content/docs/ko/getting-started/installation.md @@ -18,7 +18,7 @@ opencodex를 설치하면 같은 실행 파일을 가리키는 `ocx`와 `opencod ## 설치 ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` :::note[npm이 bun postinstall을 차단했다면?] @@ -86,7 +86,7 @@ GPT-5.6 Sol/Terra/Luna 카탈로그 정보가 이미 들어 있습니다. 다만 아닙니다. 아직 정식 배포되지 않은 opencodex 빌드를 시험할 때만 preview 채널을 사용하세요: ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md index 1534ef83f4d..2e0396dff32 100644 --- a/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md @@ -44,4 +44,4 @@ Windows에서는 opencodex가 #32111 충돌을 피하기 위해 스트리밍 응 Windows 서비스 설치·복구와 Codex shim 설치·갱신은 선택한 실행 파일이 OpenCodex 설정 루트 안에 디렉터리를 만들고 제거할 수 있는지 검사합니다. 메모리 수정이 아닌 쓰기 정책 검사입니다. 실패하면 서비스 중지, 다운로드, 스테이징, 토큰·실행 파일 쓰기 전에 거부합니다. shim 자동 복구는 안내와 함께 지연되며 시작은 계속됩니다. 정상 상태 또는 비활성화된 shim은 검사하지 않습니다. 다른 런타임을 자동으로 찾지 않습니다. 처음 하는 스케줄러 설치는 이 검사 전에 작업을 등록하고 설정 루트를 확보합니다. 거부되면 새 등록은 되돌리고, 확보한 루트는 재시도를 위해 남겨 둡니다. -> 선택한 Bun 런타임이 OpenCodex 설정 디렉터리 안에 디렉터리를 만들고 제거하지 못했습니다. Windows 애플리케이션 정책이 이 실행 파일 위치의 쓰기를 차단할 수 있습니다. `ocx` 실행 전에 정책이 허용하는 신뢰할 수 있는 Bun 실행 파일로 `OPENCODEX_BUN_PATH`를 설정하거나, `npm install -g @bitkyc08/opencodex`로 다시 설치한 뒤 재시도하세요. 다른 런타임은 선택하지 않았습니다. +> 선택한 Bun 런타임이 OpenCodex 설정 디렉터리 안에 디렉터리를 만들고 제거하지 못했습니다. Windows 애플리케이션 정책이 이 실행 파일 위치의 쓰기를 차단할 수 있습니다. `ocx` 실행 전에 정책이 허용하는 신뢰할 수 있는 Bun 실행 파일로 `OPENCODEX_BUN_PATH`를 설정하거나, `npm install -g --allow-scripts=bun @bitkyc08/opencodex`로 다시 설치한 뒤 재시도하세요. 다른 런타임은 선택하지 않았습니다. diff --git a/docs-site/src/content/docs/ru/getting-started/for-agents.md b/docs-site/src/content/docs/ru/getting-started/for-agents.md index 37917a311b3..c443d881925 100644 --- a/docs-site/src/content/docs/ru/getting-started/for-agents.md +++ b/docs-site/src/content/docs/ru/getting-started/for-agents.md @@ -14,7 +14,7 @@ description: Установите и используйте opencodex из аг Установите опубликованный пакет и убедитесь, что `ocx` доступен в `PATH`: ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/ru/getting-started/installation.md b/docs-site/src/content/docs/ru/getting-started/installation.md index ca0fe20b3e4..b48185a1c0f 100644 --- a/docs-site/src/content/docs/ru/getting-started/installation.md +++ b/docs-site/src/content/docs/ru/getting-started/installation.md @@ -19,7 +19,7 @@ opencodex устанавливает два эквивалентных имен ## Установка ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` :::note[npm заблокировал postinstall-скрипт bun?] @@ -66,7 +66,7 @@ ChatGPT, OpenAI по API-ключу, OpenRouter и эксперименталь Используйте канал preview только для тестирования ещё не выпущенных сборок opencodex: ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md index 67a7ea9c7c7..401ea3fd65e 100644 --- a/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md @@ -91,4 +91,4 @@ opencodex поставляет рантайм Bun (сейчас это **1.3.14* Установка и восстановление службы Windows, а также установка и обновление shim Codex проверяют, может ли выбранный исполняемый файл создать и удалить каталог внутри корня конфигурации OpenCodex. Это проверка политики записи, а не исправление памяти. При неудаче операция отклоняется до остановки службы, загрузки, подготовки или записи токена и запускающего файла. Автовосстановление shim откладывается с рекомендацией, запуск продолжается. Исправный или отключённый shim не проверяется. Поиск другой среды не выполняется. При первой установке через планировщик задача регистрируется и корень конфигурации закрепляется до этой проверки; при отказе новая регистрация откатывается, а закреплённый корень остаётся для повторной попытки. -> Выбранная среда Bun не смогла создать и удалить каталог конфигурации. Политика приложений Windows может запрещать запись из этого расположения. До запуска `ocx` задайте `OPENCODEX_BUN_PATH` на доверенный исполняемый файл Bun, разрешённый политикой, либо переустановите через `npm install -g @bitkyc08/opencodex` и повторите попытку. Другая среда не выбиралась. +> Выбранная среда Bun не смогла создать и удалить каталог конфигурации. Политика приложений Windows может запрещать запись из этого расположения. До запуска `ocx` задайте `OPENCODEX_BUN_PATH` на доверенный исполняемый файл Bun, разрешённый политикой, либо переустановите через `npm install -g --allow-scripts=bun @bitkyc08/opencodex` и повторите попытку. Другая среда не выбиралась. diff --git a/docs-site/src/content/docs/tr/getting-started/for-agents.md b/docs-site/src/content/docs/tr/getting-started/for-agents.md index 54e7023ccf2..7e4f295b692 100644 --- a/docs-site/src/content/docs/tr/getting-started/for-agents.md +++ b/docs-site/src/content/docs/tr/getting-started/for-agents.md @@ -15,7 +15,7 @@ kalır; bkz. [Web Kontrol Paneli](/tr/guides/web-dashboard/). Yayınlanan paketi kurun ve `ocx`'in `PATH` üzerinde olduğunu doğrulayın: ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/tr/getting-started/installation.md b/docs-site/src/content/docs/tr/getting-started/installation.md index 14f26330d21..4d410758294 100644 --- a/docs-site/src/content/docs/tr/getting-started/installation.md +++ b/docs-site/src/content/docs/tr/getting-started/installation.md @@ -20,7 +20,7 @@ duyduğunda ChatGPT oturumunuzu da kullanabilir. ## Kurulum ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` :::note[npm bun postinstall betiğini engelledi mi?] @@ -54,7 +54,7 @@ erişim sağlamaz. Önizleme kanalını yalnızca yayınlanmamış opencodex derlemelerini test etmek için kullanın: ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/tr/troubleshooting/windows-memory.md b/docs-site/src/content/docs/tr/troubleshooting/windows-memory.md index 260b35816b8..5f09debff7c 100644 --- a/docs-site/src/content/docs/tr/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/tr/troubleshooting/windows-memory.md @@ -111,4 +111,4 @@ doğrulama tam olarak budur. Windows hizmet kurulumu ve onarımı ile Codex shim kurulumu ve yenilemesi, seçilen yürütülebilir dosyanın OpenCodex yapılandırma kökünde bir dizin oluşturup kaldırabildiğini denetler. Bu bir yazma politikası denetimidir; bellek düzeltmesi değildir. Başarısızlık hizmet durdurma, indirme, hazırlama, belirteç veya başlatıcı yazma işlemlerinden önce reddedilir. Otomatik shim onarımı yönlendirmeyle ertelenir ve başlangıç devam eder. Sağlıklı veya devre dışı shim denetlenmez. Başka çalışma zamanı aranmaz. İlk zamanlayıcı kurulumu bu denetimden önce görevini kaydeder ve yapılandırma kökünü sahiplenir; ret durumunda yeni kayıt geri alınır ve sahiplenilen kök yeniden deneme için kalır. -> Seçilen Bun çalışma zamanı yapılandırma dizininde bir dizin oluşturup kaldıramadı. Windows uygulama politikası bu yürütülebilir konumundan yazmayı engelleyebilir. `ocx` başlatılmadan önce `OPENCODEX_BUN_PATH` değerini politikanızın izin verdiği güvenilir Bun yürütülebilir dosyasına ayarlayın veya `npm install -g @bitkyc08/opencodex` ile yeniden kurup tekrar deneyin. Başka çalışma zamanı seçilmedi. +> Seçilen Bun çalışma zamanı yapılandırma dizininde bir dizin oluşturup kaldıramadı. Windows uygulama politikası bu yürütülebilir konumundan yazmayı engelleyebilir. `ocx` başlatılmadan önce `OPENCODEX_BUN_PATH` değerini politikanızın izin verdiği güvenilir Bun yürütülebilir dosyasına ayarlayın veya `npm install -g --allow-scripts=bun @bitkyc08/opencodex` ile yeniden kurup tekrar deneyin. Başka çalışma zamanı seçilmedi. diff --git a/docs-site/src/content/docs/troubleshooting/windows-memory.md b/docs-site/src/content/docs/troubleshooting/windows-memory.md index 65ee95c07ab..50bb093fd21 100644 --- a/docs-site/src/content/docs/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/troubleshooting/windows-memory.md @@ -146,4 +146,4 @@ the verification this mitigation is waiting on. Windows service installation, repair and Codex shim installation/refresh check whether the selected executable can create and remove a directory inside the OpenCodex config root. This is a write-policy check, not a memory fix. Failure refuses before service stops, downloads, staging, token or launcher writes. Automatic shim repair is deferred with guidance and startup continues. Healthy or disabled shims do not probe. No alternate runtime is discovered. A first-time scheduler install registers its task and claims the config root before this check; on refusal that new registration is rolled back and the claimed root stays for a retry. -> The selected Bun runtime could not create and remove a directory in the OpenCodex config directory. Windows application policy may deny writes from this executable location. Set OPENCODEX_BUN_PATH to a trusted Bun executable allowed by your policy before launching ocx, or reinstall opencodex with npm install -g @bitkyc08/opencodex, then retry. No alternate runtime was selected. +> The selected Bun runtime could not create and remove a directory in the OpenCodex config directory. Windows application policy may deny writes from this executable location. Set OPENCODEX_BUN_PATH to a trusted Bun executable allowed by your policy before launching ocx, or reinstall opencodex with npm install -g --allow-scripts=bun @bitkyc08/opencodex, then retry. No alternate runtime was selected. diff --git a/docs-site/src/content/docs/zh-cn/getting-started/for-agents.md b/docs-site/src/content/docs/zh-cn/getting-started/for-agents.md index 80970e9601c..182b79a38ab 100644 --- a/docs-site/src/content/docs/zh-cn/getting-started/for-agents.md +++ b/docs-site/src/content/docs/zh-cn/getting-started/for-agents.md @@ -10,7 +10,7 @@ description: 为受代理驱动或脚本控制的终端安装并操作 opencodex 安装已发布的包,并确认 `ocx` 已在 `PATH` 中: ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/zh-cn/getting-started/installation.md b/docs-site/src/content/docs/zh-cn/getting-started/installation.md index 335debd845d..e4d017e72b3 100644 --- a/docs-site/src/content/docs/zh-cn/getting-started/installation.md +++ b/docs-site/src/content/docs/zh-cn/getting-started/installation.md @@ -18,7 +18,7 @@ vision 和网络搜索 sidecar 也可以使用你的 ChatGPT 登录凭据。 ## 安装 ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` :::note[npm 拦截了 bun postinstall?] @@ -61,7 +61,7 @@ GPT-5.6 Sol/Terra/Luna 目录信息,但这些条目本身不会授予上游模 opencodex 构建时,才需要使用 preview 渠道: ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md b/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md index b77e5f2d7c1..bde42e03b21 100644 --- a/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md @@ -44,4 +44,4 @@ opencodex 打包了 Bun 运行时(当前为 **1.3.14**)。这类内存增长 Windows 服务安装、修复及 Codex shim 安装、刷新会检查所选可执行文件能否在 OpenCodex 配置根目录内创建并删除目录。这是写入策略检查,并非内存修复。失败时会在停止服务、下载、暂存以及写入令牌或启动器前拒绝操作。shim 自动修复会延后并显示指引,启动继续。正常或禁用的 shim 不会检查。不自动寻找其他运行时。 首次计划任务安装会在此检查前注册任务并认领配置根目录;被拒绝时会回滚新注册,已认领的根目录保留以便重试。 -> 所选 Bun 运行时无法在配置目录内创建并删除目录。Windows 应用策略可能禁止来自此可执行文件位置的写入。在启动 `ocx` 前,将 `OPENCODEX_BUN_PATH` 设置为策略允许的可信 Bun 可执行文件,或通过 `npm install -g @bitkyc08/opencodex` 重新安装后重试。未选择其他运行时。 +> 所选 Bun 运行时无法在配置目录内创建并删除目录。Windows 应用策略可能禁止来自此可执行文件位置的写入。在启动 `ocx` 前,将 `OPENCODEX_BUN_PATH` 设置为策略允许的可信 Bun 可执行文件,或通过 `npm install -g --allow-scripts=bun @bitkyc08/opencodex` 重新安装后重试。未选择其他运行时。 diff --git a/docs-site/src/content/docs/zh-tw/getting-started/for-agents.md b/docs-site/src/content/docs/zh-tw/getting-started/for-agents.md index 174f4447fcc..1438fab0353 100644 --- a/docs-site/src/content/docs/zh-tw/getting-started/for-agents.md +++ b/docs-site/src/content/docs/zh-tw/getting-started/for-agents.md @@ -10,7 +10,7 @@ description: 從 agent 驅動或腳本化終端機安裝並操作 opencodex, 安裝已發布的套件,並確認 `ocx` 在 `PATH` 上: ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx --version ``` diff --git a/docs-site/src/content/docs/zh-tw/getting-started/installation.md b/docs-site/src/content/docs/zh-tw/getting-started/installation.md index f954cb7175a..1e2e1018e4b 100644 --- a/docs-site/src/content/docs/zh-tw/getting-started/installation.md +++ b/docs-site/src/content/docs/zh-tw/getting-started/installation.md @@ -18,7 +18,7 @@ vision 和網路搜尋 sidecar 也可以使用你的 ChatGPT 登入憑證。 ## 安裝 ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ``` :::note[npm 攔截了 bun postinstall?] @@ -50,7 +50,7 @@ GPT-5.6 Sol/Terra/Luna 目錄資訊,但這些條目本身不會授予上游模 opencodex 建置時,才需要使用 preview 渠道: ```bash -npm install -g @bitkyc08/opencodex@preview +npm install -g --allow-scripts=bun @bitkyc08/opencodex@preview ocx update --tag preview ``` diff --git a/docs-site/src/content/docs/zh-tw/troubleshooting/windows-memory.md b/docs-site/src/content/docs/zh-tw/troubleshooting/windows-memory.md index 58f1d32c023..bc8cdf7e7e3 100644 --- a/docs-site/src/content/docs/zh-tw/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/zh-tw/troubleshooting/windows-memory.md @@ -44,4 +44,4 @@ opencodex 內嵌 Bun runtime(目前為 **1.3.14**)。記憶體成長由已 Windows 服務安裝、修復及 Codex shim 安裝、更新會檢查所選執行檔能否在 OpenCodex 設定根目錄內建立並移除目錄。這是寫入政策檢查,而非記憶體修復。失敗時會在停止服務、下載、暫存以及寫入權杖或啟動器前拒絕操作。shim 自動修復會延後並顯示指引,啟動繼續。正常或停用的 shim 不會檢查。不自動尋找其他 runtime。 首次排程安裝會在此檢查前登錄工作並認領設定根目錄;被拒絕時會復原新登錄,已認領的根目錄保留以便重試。 -> 所選 Bun runtime 無法在設定目錄內建立並移除目錄。Windows 應用程式政策可能禁止來自此執行檔位置的寫入。在啟動 `ocx` 前,將 `OPENCODEX_BUN_PATH` 設為政策允許的可信 Bun 執行檔,或以 `npm install -g @bitkyc08/opencodex` 重新安裝後重試。未選擇其他 runtime。 +> 所選 Bun runtime 無法在設定目錄內建立並移除目錄。Windows 應用程式政策可能禁止來自此執行檔位置的寫入。在啟動 `ocx` 前,將 `OPENCODEX_BUN_PATH` 設為政策允許的可信 Bun 執行檔,或以 `npm install -g --allow-scripts=bun @bitkyc08/opencodex` 重新安裝後重試。未選擇其他 runtime。 diff --git a/readme/README.fr.md b/readme/README.fr.md index 1a6820fbd91..fcb05695ccf 100644 --- a/readme/README.fr.md +++ b/readme/README.fr.md @@ -15,7 +15,7 @@

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -88,7 +88,7 @@ opencodex est une passerelle locale légère entre vos outils de programmation e ### Installation personnelle (CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18+ ; le runtime Bun est inclus automatiquement +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18+ ; le runtime Bun est inclus automatiquement ocx start # proxy + tableau de bord sur localhost:10100 ``` @@ -250,7 +250,7 @@ leur arrivée dans le paquet npm. Pour les agents ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # ou `ocx service` ocx init # configuration interactive : écrit ~/.opencodex/config.json et relie Codex ``` diff --git a/readme/README.ja.md b/readme/README.ja.md index 734f849f17d..ba4b91408b5 100644 --- a/readme/README.ja.md +++ b/readme/README.ja.md @@ -15,7 +15,7 @@

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -88,7 +88,7 @@ opencodex は、コーディングツールとモデルプロバイダーをつ ### 個人向けインストール(CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18 以上。Bun ランタイムは自動で同梱されます +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18 以上。Bun ランタイムは自動で同梱されます ocx start # プロキシとダッシュボードが localhost:10100 で起動 ``` @@ -246,7 +246,7 @@ bun run src/cli/index.ts start エージェント向け ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # または `ocx service` ocx init # 対話式セットアップ: ~/.opencodex/config.json を書き、Codex を接続します ``` diff --git a/readme/README.ko.md b/readme/README.ko.md index 6586139d130..b631de84ad8 100644 --- a/readme/README.ko.md +++ b/readme/README.ko.md @@ -15,7 +15,7 @@

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -88,7 +88,7 @@ opencodex는 코딩 도구와 모델 프로바이더를 연결하는 가벼운 ### 개인 설치 (CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18+; Bun 런타임은 자동으로 번들됩니다 +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18+; Bun 런타임은 자동으로 번들됩니다 ocx start # 프록시 + 대시보드: localhost:10100 ``` @@ -241,7 +241,7 @@ bun run src/cli/index.ts start 에이전트용 ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # 또는 `ocx service` ocx init # 대화형 설정: ~/.opencodex/config.json을 쓰고 Codex를 연결합니다 ``` diff --git a/readme/README.ru.md b/readme/README.ru.md index 0b1d8184243..c1ac691fe02 100644 --- a/readme/README.ru.md +++ b/readme/README.ru.md @@ -15,7 +15,7 @@

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -88,7 +88,7 @@ opencodex — лёгкий локальный шлюз между вашими ### Личная установка (CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18+; рантайм Bun подключается автоматически +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18+; рантайм Bun подключается автоматически ocx start # прокси + панель управления на localhost:10100 ``` @@ -252,7 +252,7 @@ bun run src/cli/index.ts start Для агентов ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # или `ocx service` ocx init # интерактивная настройка: пишет ~/.opencodex/config.json и подключает Codex ``` diff --git a/readme/README.tr.md b/readme/README.tr.md index 8bb58c01bcd..216c2d0f570 100644 --- a/readme/README.tr.md +++ b/readme/README.tr.md @@ -15,7 +15,7 @@

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -88,7 +88,7 @@ opencodex, kodlama araçlarınızla model sağlayıcılarınız arasında çalı ### Kişisel kurulum (CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18+; Bun çalışma zamanı otomatik olarak paketlenir +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18+; Bun çalışma zamanı otomatik olarak paketlenir ocx start # proxy + kontrol paneli, localhost:10100 ``` @@ -248,7 +248,7 @@ iyileştirmeleri ve henüz yayımlanmamış düzeltmeler npm paketine ulaşmadan Ajanlar için ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # ya da `ocx service` ocx init # etkileşimli kurulum: ~/.opencodex/config.json yazar ve Codex'i bağlar ``` diff --git a/readme/README.zh-CN.md b/readme/README.zh-CN.md index 1c0a6ef79b6..13d4c9317a1 100644 --- a/readme/README.zh-CN.md +++ b/readme/README.zh-CN.md @@ -15,7 +15,7 @@

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -88,7 +88,7 @@ opencodex 是连接编程工具与模型提供商的轻量级本地网关。它 ### 个人安装(CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18+;Bun 运行时会自动捆绑 +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18+;Bun 运行时会自动捆绑 ocx start # 代理 + 仪表板:localhost:10100 ``` @@ -241,7 +241,7 @@ bun run src/cli/index.ts start 面向 agent ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # 或 `ocx service` ocx init # 交互式设置:写入 ~/.opencodex/config.json 并接入 Codex ``` diff --git a/readme/README.zh-TW.md b/readme/README.zh-TW.md index b48d492e0d5..7843c589bf6 100644 --- a/readme/README.zh-TW.md +++ b/readme/README.zh-TW.md @@ -15,7 +15,7 @@

```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start ``` @@ -88,7 +88,7 @@ opencodex 是連接程式開發工具與模型供應商的輕量級本機閘道 ### 個人安裝(CLI) ```bash -npm install -g @bitkyc08/opencodex # Node 18+;Bun 執行環境會自動打包 +npm install -g --allow-scripts=bun @bitkyc08/opencodex # Node 18+;Bun 執行環境會自動打包 ocx start # 代理 + 儀表板位於 localhost:10100 ``` @@ -241,7 +241,7 @@ bun run src/cli/index.ts start 給 agent 使用 ```bash -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex ocx start # 或 `ocx service` ocx init # 互動式設定:寫入 ~/.opencodex/config.json 並接上 Codex ``` diff --git a/readme/i18n-manifest.json b/readme/i18n-manifest.json index 7fc9221d996..cb937aa02f5 100644 --- a/readme/i18n-manifest.json +++ b/readme/i18n-manifest.json @@ -6,43 +6,43 @@ "file": "readme/README.fr.md", "label": "Français", "docsPath": "fr", - "sourceSha256": "317fdcc0b4e903303817e820afd6fd620dccbcee9ab0caf9e64fd87e9a966682" + "sourceSha256": "941205d861dcfa525b40c8a70ed89ede49b9591d97b30e695aaff066f70f379d" }, "ko": { "file": "readme/README.ko.md", "label": "한국어", "docsPath": "ko", - "sourceSha256": "317fdcc0b4e903303817e820afd6fd620dccbcee9ab0caf9e64fd87e9a966682" + "sourceSha256": "941205d861dcfa525b40c8a70ed89ede49b9591d97b30e695aaff066f70f379d" }, "zh-CN": { "file": "readme/README.zh-CN.md", "label": "简体中文", "docsPath": "zh-cn", - "sourceSha256": "317fdcc0b4e903303817e820afd6fd620dccbcee9ab0caf9e64fd87e9a966682" + "sourceSha256": "941205d861dcfa525b40c8a70ed89ede49b9591d97b30e695aaff066f70f379d" }, "zh-TW": { "file": "readme/README.zh-TW.md", "label": "繁體中文", "docsPath": "zh-tw", - "sourceSha256": "317fdcc0b4e903303817e820afd6fd620dccbcee9ab0caf9e64fd87e9a966682" + "sourceSha256": "941205d861dcfa525b40c8a70ed89ede49b9591d97b30e695aaff066f70f379d" }, "ru": { "file": "readme/README.ru.md", "label": "Русский", "docsPath": "ru", - "sourceSha256": "317fdcc0b4e903303817e820afd6fd620dccbcee9ab0caf9e64fd87e9a966682" + "sourceSha256": "941205d861dcfa525b40c8a70ed89ede49b9591d97b30e695aaff066f70f379d" }, "ja": { "file": "readme/README.ja.md", "label": "日本語", "docsPath": "ja", - "sourceSha256": "317fdcc0b4e903303817e820afd6fd620dccbcee9ab0caf9e64fd87e9a966682" + "sourceSha256": "941205d861dcfa525b40c8a70ed89ede49b9591d97b30e695aaff066f70f379d" }, "tr": { "file": "readme/README.tr.md", "label": "Türkçe", "docsPath": "tr", - "sourceSha256": "317fdcc0b4e903303817e820afd6fd620dccbcee9ab0caf9e64fd87e9a966682" + "sourceSha256": "941205d861dcfa525b40c8a70ed89ede49b9591d97b30e695aaff066f70f379d" } } } diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 1da608a5b99..0b3ab9c3fd5 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -22,15 +22,15 @@ if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { Write-Host "Using Node v$nodeVersion" -# Install opencodex globally -# If npm reports "install scripts blocked" for bun, rerun as: -# npm install -g --allow-scripts=bun @bitkyc08/opencodex +# Install opencodex globally. npm 12 blocks dependency lifecycle scripts by default, and +# bun's postinstall is what unpacks the bundled runtime; without this approval the install +# succeeds but `ocx` cannot start (#8361). npm 8 through 11 accept the flag on global installs. # (use an elevated PowerShell if the original install was elevated) $npm = Get-Command npm.cmd -ErrorAction SilentlyContinue if (-not $npm) { $npm = Get-Command npm -ErrorAction Stop } -& $npm.Source install -g @bitkyc08/opencodex +& $npm.Source install -g --allow-scripts=bun @bitkyc08/opencodex if ($LASTEXITCODE -ne 0) { Write-Error "npm install failed with exit code $LASTEXITCODE" exit $LASTEXITCODE diff --git a/scripts/install.sh b/scripts/install.sh index 6a25db452e9..54715adc9c7 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -21,11 +21,11 @@ fi echo "Using Node $(node --version)" -# Install opencodex globally -# If npm reports "install scripts blocked" for bun, rerun as: -# npm install -g --allow-scripts=bun @bitkyc08/opencodex +# Install opencodex globally. npm 12 blocks dependency lifecycle scripts by default, and +# bun's postinstall is what unpacks the bundled runtime; without this approval the install +# succeeds but `ocx` cannot start (#8361). npm 8 through 11 accept the flag on global installs. # (keep sudo if the original install used sudo) -npm install -g @bitkyc08/opencodex +npm install -g --allow-scripts=bun @bitkyc08/opencodex if ! command -v ocx &>/dev/null; then NPM_BIN="$(npm bin -g 2>/dev/null || printf "%s/bin" "$(npm prefix -g)")" diff --git a/scripts/test-layout/layout.json b/scripts/test-layout/layout.json index aed2989c321..867e3ee78d5 100644 --- a/scripts/test-layout/layout.json +++ b/scripts/test-layout/layout.json @@ -925,7 +925,7 @@ "docs-readme-memory-inventory.test.ts": "ci-workflows", "docs-readme-translation-parity.test.ts": "ci-workflows", "docs-remote-hub-claims.test.ts": "ci-workflows", - "doctor-codex-envkey-readiness.test.ts": "service", + "doctor-codex-envkey-readiness.test.ts": "service", "doctor-bun-install.test.ts": "service", "doctor-oauth.test.ts": "service", "doctor-provider-apikey.test.ts": "service", "doctor-service-memory-contract.test.ts": "service", @@ -1839,7 +1839,7 @@ "update-async-routes.test.ts": "server", "update-badge.test.ts": "update", "update-bounded-json.test.ts": "update", "update-checkpoint-codec.test.ts": "update", "update-contracts.test.ts": "update", "update-candidate-id.test.ts": "update", "update-external-metadata.test.ts": "update", "update-minisign.test.ts": "update", "update-root-snapshot.test.ts": "update", "update-signed-supply.test.ts": "update", "update-signed-version.test.ts": "update", "update-trust-root.test.ts": "update", "update-bun-ownership-lease.test.ts": "update", "update-desktop-badge.test.ts": "update", "update-desktop-owner.test.ts": "update", "update-supervised-recovery.test.ts": "update", - "update-job.test.ts": "update", "update-recovery-host-free.test.ts": "update", "update-job-supervised-restart.test.ts": "update", "update-standalone.test.ts": "update", "update-worker-launch.test.ts": "update", + "update-job.test.ts": "update", "update-recovery-host-free.test.ts": "update", "update-job-supervised-restart.test.ts": "update", "update-standalone.test.ts": "update", "update-worker-launch.test.ts": "update", "update-npm-allow-scripts.test.ts": "update", "update-notify.test.ts": "update", "update-npm-cache-preflight.test.ts": "update", "update-orchestrated-session.test.ts": "update", "update-orchestrated-entry.test.ts": "update", "update-orchestrated-apply.test.ts": "update", "update-orchestrated-arguments.test.ts": "update", "update-orchestrated-plan.test.ts": "update", "update-sync-mutex.test.ts": "update", "update-orchestrated-identity.test.ts": "update", "update-orchestrated-token.test.ts": "update", "update-npm-invocation.test.ts": "update", "update-npm-timeout.test.ts": "update", "update-npm-supervisor-exit.test.ts": "update", "update-mise-launcher-target.test.ts": "update", "update-mise.test.ts": "update", "update-mise-node-runtime.test.ts": "update", diff --git a/src/cli/bundled-bun-diagnostic.ts b/src/cli/bundled-bun-diagnostic.ts new file mode 100644 index 00000000000..d9d8535a83c --- /dev/null +++ b/src/cli/bundled-bun-diagnostic.ts @@ -0,0 +1,91 @@ +/** + * `ocx doctor` check for a package install whose bundled Bun dependency never finished + * installing (#8361). + * + * npm 12 blocks dependency lifecycle scripts by default. The `bun` npm package downloads nothing + * in its tarball: its postinstall copies the selected `@oven/bun-*` executable into `bin/`. An + * install that skipped that script succeeds and leaves a ~450-byte placeholder, so the launcher + * fails later with "the `bun` dependency is not installed". Doctor is where an operator looks + * after that failure, so it names the state and the exact command that repairs it. + * + * The check is observe-only and never fails the doctor run: a leftover PATH Bun or an explicit + * `OPENCODEX_BUN_PATH` can still be serving requests, which makes this a degraded install rather + * than an unusable surface. + */ +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import * as bunRuntime from "../lib/bun-runtime"; +import * as installDetection from "../update/install-detection.mjs"; + +const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."); + +export const BUNDLED_BUN_MISSING_DETAIL = "the `bun` dependency is not installed"; +export const NPM_BUN_REPAIR_COMMAND = "npm install -g --allow-scripts=bun @bitkyc08/opencodex"; + +type PackageInstaller = "npm" | "pnpm" | "bun" | "mise"; + +export type BundledBunDiagnostic = + | { status: "ready"; path: string } + | { status: "not-applicable" } + | { status: "missing"; installer: PackageInstaller; lines: string[] }; + +export interface BundledBunDiagnosticDeps { + platform?: NodeJS.Platform; + bundledPath?: () => string | null; + detectOwnership?: () => { installer: string; owner?: { tool: string } | null }; +} + +/** The command that reinstalls this package while allowing bun's postinstall. */ +export function bundledBunRepairCommand(installer: PackageInstaller, tool?: string | null): string | null { + if (installer === "npm") return NPM_BUN_REPAIR_COMMAND; + if (installer === "pnpm") return "pnpm add -g --allow-build=bun @bitkyc08/opencodex"; + if (installer === "bun") return "bun add -g @bitkyc08/opencodex"; + return tool ? `mise upgrade ${tool}` : null; +} + +export function collectBundledBunDiagnostic(deps: BundledBunDiagnosticDeps = {}): BundledBunDiagnostic { + const bundled = (deps.bundledPath ?? bunRuntime.bundledBunPath)(); + if (bundled) return { status: "ready", path: bundled }; + + const ownership = (deps.detectOwnership + ?? (() => installDetection.detectInstallOwnershipFromPath(PACKAGE_ROOT)))(); + const installer = ownership.installer; + // Only the mise verdict carries an owner; the others have no tool to name. + const ownerTool = "owner" in ownership ? ownership.owner?.tool ?? null : null; + // A source checkout has no bundled dependency by design, and a standalone binary carries its + // own runtime; neither is a skipped-postinstall symptom. + if (installer !== "npm" && installer !== "pnpm" && installer !== "bun" && installer !== "mise") { + return { status: "not-applicable" }; + } + + const repair = bundledBunRepairCommand(installer, ownerTool); + const windows = (deps.platform ?? process.platform) === "win32"; + const lines = [ + ` !! ${BUNDLED_BUN_MISSING_DETAIL} (its postinstall was skipped or the dependency is incomplete)`, + " Allow bun's own install script; npm 12 blocks lifecycle scripts by default,", + " and --ignore-scripts or ignore-scripts=true in an npmrc blocks it on any npm", + ]; + if (!repair) { + lines.push(" This mise-owned installation has no verified tool name; upgrade it through mise."); + return { status: "missing", installer, lines }; + } + if (windows && installer === "npm") { + // A running bun.exe holds node_modules/bun/bin open, so npm fails with EBUSY. The instruction + // has to stop first; `ocx update` does this itself, but a manual reinstall does not. + lines.push(" Stop the proxy first so Windows releases node_modules/bun/bin, then run:"); + lines.push(" ocx stop"); + lines.push(` ${repair}`); + lines.push(" Restart afterwards: ocx service restart (or ocx start when unmanaged)"); + } else { + lines.push(" Fix: reinstall with the package manager that owns this install:"); + lines.push(` ${repair}`); + } + return { status: "missing", installer, lines }; +} + +/** Render the doctor "Bundled Bun runtime" section lines. */ +export function formatBundledBunDiagnosticLines(diagnostic: BundledBunDiagnostic): string[] { + if (diagnostic.status === "ready") return [` ok ready: ${diagnostic.path}`]; + if (diagnostic.status === "not-applicable") return []; + return diagnostic.lines; +} diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index 3dea81c1020..04421d3ac65 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -8,6 +8,7 @@ * networking. See devlog/_plan/260630_wsl-account-autoswitch/30_*. */ import { cliCommandDoctorChecks, collectCliPathDiagnostics } from "./cli-path-diagnostics"; +import { collectBundledBunDiagnostic, formatBundledBunDiagnosticLines } from "./bundled-bun-diagnostic"; import { accessSync, constants, existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { dirname, join } from "node:path"; @@ -1607,6 +1608,16 @@ export async function runDoctor(args: string[] = []): Promise { } } + // The bundled dependency is the install's own runtime, distinct from whatever Bun is running + // doctor or serving the proxy. A skipped postinstall (#8361) is invisible here otherwise. + // A source checkout or standalone binary has no bundled `bun` dependency, so the + // section has nothing to say there and prints no heading. + const bundledBunLines = formatBundledBunDiagnosticLines(collectBundledBunDiagnostic()); + if (bundledBunLines.length > 0) { + console.log("\nBundled Bun runtime"); + for (const line of bundledBunLines) console.log(line); + } + console.log("\nWHAM reachability"); const probe = await probeWham(); const detail = probe.status !== null ? `status=${probe.status}` : `error=${probe.classification}`; diff --git a/src/lib/bun-runtime-preflight.ts b/src/lib/bun-runtime-preflight.ts index 3f3e7f6a68b..eb71a7551d4 100644 --- a/src/lib/bun-runtime-preflight.ts +++ b/src/lib/bun-runtime-preflight.ts @@ -9,7 +9,7 @@ export const RUNTIME_PREFLIGHT_TIMEOUT_MS = 5_000; export const RUNTIME_PREFLIGHT_GUIDANCE = "The selected Bun runtime could not create and remove a directory in the OpenCodex config directory. " + "Windows application policy may deny writes from this executable location. " + "Set OPENCODEX_BUN_PATH to a trusted Bun executable allowed by your policy before launching ocx, " - + "or reinstall opencodex with npm install -g @bitkyc08/opencodex, then retry. No alternate runtime was selected."; + + "or reinstall opencodex with npm install -g --allow-scripts=bun @bitkyc08/opencodex, then retry. No alternate runtime was selected."; export class RuntimePreflightError extends Error { readonly code = "OCX_RUNTIME_PREFLIGHT_FAILED"; diff --git a/src/update/index.ts b/src/update/index.ts index 2b380ea3ed0..68b5b101491 100644 --- a/src/update/index.ts +++ b/src/update/index.ts @@ -364,7 +364,13 @@ export function updateCommand(installer: Installer, tag: Channel, resolvedVersio return { bin: "pnpm", args: ["add", "-g", "--allow-build=bun", `${PKG}@${target}`] }; } const bin = "npm"; - const args = ["install", "-g", `${PKG}@${target}`]; + // npm 12 blocks dependency lifecycle scripts by default, and bun's postinstall is what + // copies the selected @oven/bun-* executable into bun/bin. Without this narrow approval + // the install succeeds but leaves bun's un-downloaded placeholder, so the next `ocx` + // cannot start (#8361). npm 6 through 11 run bun's postinstall anyway and install with the + // flag on global installs, so it is unconditional rather than version-gated; some npm 11 + // releases print an "Unknown cli config" warning for it and still install. + const args = ["install", "-g", "--allow-scripts=bun", `${PKG}@${target}`]; return { bin, args }; } diff --git a/structure/ops/desktop-supervision.md b/structure/ops/desktop-supervision.md index 1f4c2d9f216..fb27b68cc17 100644 --- a/structure/ops/desktop-supervision.md +++ b/structure/ops/desktop-supervision.md @@ -127,6 +127,15 @@ formatting is the only allowlisted work between that authorizer and process crea `tests/update/update-home-launcher.test.ts` and `tests/update/update-home-recovery.test.ts` exercise refusal when preparation changes the home observation, plus stable-home starts. +The npm install argv that `updateCommand()` (and the Node launcher's own npm self-update) builds +always carries `--allow-scripts=bun`: npm 12 blocks dependency lifecycle scripts by default, and +bun's postinstall is what unpacks the bundled runtime, so a bare global install would leave the +next `ocx` unable to start (#8361). npm 8 through 11 accept the flag on global installs, so it is +not version-gated. The npm package replacement stages beside the live tree and stops the proxy +before the swap, which is also what releases `node_modules/bun/bin` on Windows; a manual +`npm install -g` under a live proxy still fails with `EBUSY`, so doctor prints the stop, install +and restart sequence for npm-owned installs instead of attempting it. + The bypass ledger classifies these guards as **early warning**, not enforcement: | Guard | Surface / tier | Known limit and residual risk | Pass-through assertion | diff --git a/structure/ops/service-and-sidecars.md b/structure/ops/service-and-sidecars.md index 2bb82bb72e1..70349480a36 100644 --- a/structure/ops/service-and-sidecars.md +++ b/structure/ops/service-and-sidecars.md @@ -100,7 +100,7 @@ The frozen selection feeds scheduler rendering, the WinSW entry and the install- The fresh scheduler path captures root absence and freezes the runtime at entry, stages and registers its definition, removes staging and claims config ownership, then probes with `rootWasAbsent: false` immediately before `prepare()`; a refused probe rolls back the new registration before service-manager cleanup or asset/state publication. Healthy or disabled shims do not probe; refused automatic restore defers with guidance and startup continues. Non-Windows admission performs no spawn or filesystem work, selection stays pre-dotenv and `cliEntry` stays I/O-free. That cleanup (`prepareServiceInstall`) ends every recorded, conflicting and requested manager, then — Windows only — issues the same best-effort launcher/wrapper termination `ocx stop` performs, scoped to this install's canonical script and launcher tokens (an `schtasks /end` survivor can respawn the proxy mid-install; this layer guarantees neither that the wrapper actually exited nor that the new instance comes up), then stops the tracked standalone listener; a refused or unverifiable manager stop aborts before that cleanup runs. An absent root reaching admission is created with an exclusive non-recursive mode-0700 mkdir under an existing parent and left empty for later ownership claiming; the preflight never deletes the config root (refusal, concurrent creation, contents and replacements are all preserved). Existing roots must be real directories, not files or symlinks/junctions, and probe cleanup is non-recursive. -A standalone selection is admitted in-process only when its path equals `process.execPath` exactly (standalone is executable packaging, not Desktop ownership); that path has neither child isolation nor a timeout, so synchronous filesystem operations may block, and other standalone selections refuse. Recovery is an operator-selected trusted `OPENCODEX_BUN_PATH` before launching ocx, or an `npm install -g @bitkyc08/opencodex` reinstall followed by retry; no runtime discovery or probe memo is added. +A standalone selection is admitted in-process only when its path equals `process.execPath` exactly (standalone is executable packaging, not Desktop ownership); that path has neither child isolation nor a timeout, so synchronous filesystem operations may block, and other standalone selections refuse. Recovery is an operator-selected trusted `OPENCODEX_BUN_PATH` before launching ocx, or an `npm install -g --allow-scripts=bun @bitkyc08/opencodex` reinstall followed by retry; no runtime discovery or probe memo is added. The flag is part of the contract, not advice: npm 12 blocks dependency lifecycle scripts by default and bun's postinstall is what unpacks the binary, while npm 8 through 11 accept the flag on a global install (#8361). ## Windows npm tray update badge diff --git a/structure/runtime.md b/structure/runtime.md index 1aa77595d12..749a67f4baa 100644 --- a/structure/runtime.md +++ b/structure/runtime.md @@ -121,7 +121,7 @@ does not perform OAuth, and runtime credential resolution rereads the owned sour | `src/config/admitted-identity.ts` | Which configuration a derived artifact was built from. Detaches the resident configuration as plain data so one pass cannot gather under one state and project under another, and records the complete structure beside the configuration file's bytes. Canonical identity tags arrays and objects separately, so key sorting cannot equate an object with an array of entry pairs. Refuses an accessor, a cycle, a value JSON could not produce, an unreadable file and a file the loader would have had to salvage; a callable `providers[name].fetch` is the one non-data field, held and compared by reference, while a written one is ordinary data on both sides, as the outbound transport also reads it. It does NOT require the resident configuration to equal the file: the proxy routes by what it holds, and live reconciliation retains live changes and the active listener binding on purpose. Evidence stays in a module WeakMap, never on the config and never in a response. | | `src/server/ports.ts` / `src/server/bind-host.ts` | Own bind availability, ephemeral-port selection and startup hostname normalization shared with liveness: blank and case-insensitive `localhost`/`localhost.` bind to IPv4 loopback; other spellings remain unchanged. Temporary probes dispose accepted peers and wait for listener close before reporting success. | | `src/cli/status.ts` / `src/cli/status-probes.ts` | Status snapshot assembly and the shared read-only health/stale-process probes used by status and doctor. Probe evidence keeps recorded-port choice, before/after snapshots and per-call timer cleanup together. | -| `src/cli/doctor.ts` | Read-only environment diagnostics. Sections print through `console.log`; each is a `collect*` helper above `runDoctor` so it is testable without the command. Only a `FAIL`-level condition records a doctor failure — a degraded-but-working install must not break a green pipeline. `collectDefaultModelExposure` compares Codex's root `model` pin against the exposed set, which it READS rather than recomputes: the running proxy's `/v1/models` through the byte-capped direct-local transport when one answers, otherwise the on-disk catalog's `visibility: "list"` slugs. It reports exposed, not exposed, or undeterminable, and never the second when it could not read either surface. | +| `src/cli/doctor.ts` | Read-only environment diagnostics. Sections print through `console.log`; each is a `collect*` helper above `runDoctor` so it is testable without the command. Only a `FAIL`-level condition records a doctor failure — a degraded-but-working install must not break a green pipeline. `collectDefaultModelExposure` compares Codex's root `model` pin against the exposed set, which it READS rather than recomputes: the running proxy's `/v1/models` through the byte-capped direct-local transport when one answers, otherwise the on-disk catalog's `visibility: "list"` slugs. It reports exposed, not exposed, or undeterminable, and never the second when it could not read either surface. `src/cli/bundled-bun-diagnostic.ts` is the separate "Bundled Bun runtime" section: when `bundledBunPath()` finds no real binary it asks the package-layout detector who owns the install and prints that manager's repair command, because a skipped npm postinstall (npm 12 blocks lifecycle scripts by default) otherwise surfaces only later as the launcher's "the `bun` dependency is not installed" (#8361). It is observe-only and records no doctor failure — a PATH Bun or `OPENCODEX_BUN_PATH` can still serve — and on Windows the npm guidance stops the proxy before reinstalling, since a live `bun.exe` holds `node_modules/bun/bin` open. | | `src/router.ts` | Provider/model selection before adapter dispatch. Policy execution and ordinary management dry-run share effective-provider capability evidence; unresolved, missing, and disabled providers are excluded before scoring. | | `src/providers/api-key-selection-capture.ts` | Pure request-owned snapshot of the configured key entry, reference, and revision. The router and stateful selection module share this leaf with type-only dependencies; `api-key-selection.ts` retains the compatibility export and owns persisted selection changes. `src/providers/routed-config.ts` owns request-time provider/registry resolution and the discarded-base-URL warning memo; key selection imports that leaf instead of the router, while `src/router.ts` retains the resolver and warning-reconciliation compatibility exports. The leaf preserves GCP service-account credential markers verbatim until the Vertex adapter resolves the selected account per request. | | `src/types.ts` | Shared config, parsed request, adapter, and event types. | diff --git a/tests/ci-workflows/install-scripts.test.ts b/tests/ci-workflows/install-scripts.test.ts index 4d3674c44f6..dd3841279c9 100644 --- a/tests/ci-workflows/install-scripts.test.ts +++ b/tests/ci-workflows/install-scripts.test.ts @@ -142,7 +142,9 @@ describe("install scripts", () => { const script = await readText("scripts/install.sh"); expect(script).toContain("Node.js 18+ is required"); - expect(script).toContain("npm install -g @bitkyc08/opencodex"); + // #8361: npm 12 blocks bun's postinstall by default, so a bare install produces a + // package whose bundled runtime never unpacked. Both installers must permit that one script. + expect(script).toContain("npm install -g --allow-scripts=bun @bitkyc08/opencodex"); expect(script).toContain("command -v ocx"); expect(script).toContain("ocx help"); expect(script).not.toContain("bun install -g @bitkyc08/opencodex"); @@ -153,7 +155,7 @@ describe("install scripts", () => { const script = await readText("scripts/install.ps1"); expect(script).toContain("Node.js 18+ is required"); - expect(script).toContain("& $npm.Source install -g @bitkyc08/opencodex"); + expect(script).toContain("& $npm.Source install -g --allow-scripts=bun @bitkyc08/opencodex"); expect(script).toContain("$LASTEXITCODE"); expect(script).toContain("Get-Command ocx.cmd"); expect(script).toContain("Get-Command ocx"); @@ -219,7 +221,9 @@ exit 0 expect(launcher).toContain('process.argv[2] === "update"'); expect(launcher).toContain('const selector = child?.version ?? tag;'); - expect(launcher).toContain('["install", "-g", `${PKG}@${selector}`]'); + // #8361: the launcher's own npm reinstall must allow bun's postinstall for the same + // reason the shipped installers do; a bare install leaves the runtime un-unpacked. + expect(launcher).toContain('["install", "-g", "--allow-scripts=bun", `${PKG}@${selector}`]'); expect(launcher).toContain('["add", "-g", "--allow-build=bun", `${PKG}@${selector}`]'); expect(launcher).toContain('const tag = updateTag(current);'); expect(launcher).toContain('return String(currentVersion).includes("-preview.") ? "preview" : "latest"'); diff --git a/tests/codex-integration/codex-shim-runtime-preflight.test.ts b/tests/codex-integration/codex-shim-runtime-preflight.test.ts index c4ccf7b0cf2..bca8ac902d0 100644 --- a/tests/codex-integration/codex-shim-runtime-preflight.test.ts +++ b/tests/codex-integration/codex-shim-runtime-preflight.test.ts @@ -61,7 +61,8 @@ describe("Windows shim selected-runtime preflight", () => { expect(maybeAutoRestoreCodexShim("start", ["start"], { env: {}, warn: message => warnings.push(message), restore: () => result, readConfig: () => ({ config: getDefaultConfig(), source: "default", error: null }), })).toBeUndefined(); - expect(warnings).toEqual([expect.stringContaining("npm install -g @bitkyc08/opencodex")]); + // #8361: the repair hint must approve bun's postinstall; npm 12 skips it by default. + expect(warnings).toEqual([expect.stringContaining("npm install -g --allow-scripts=bun @bitkyc08/opencodex")]); })); } diff --git a/tests/fixtures/test-layout-expected.json b/tests/fixtures/test-layout-expected.json index 58e03d7dd46..b80bc33ec35 100644 --- a/tests/fixtures/test-layout-expected.json +++ b/tests/fixtures/test-layout-expected.json @@ -855,6 +855,7 @@ "docs-provider-discovery-limits.test.ts": "ci-workflows", "docs-provider-preset-counts.test.ts": "ci-workflows", "docs-readme-memory-inventory.test.ts": "ci-workflows", "docs-readme-translation-parity.test.ts": "ci-workflows", "docs-remote-hub-claims.test.ts": "ci-workflows", "doctor-codex-envkey-readiness.test.ts": "service", + "doctor-bun-install.test.ts": "service", "doctor-oauth.test.ts": "service", "doctor-provider-apikey.test.ts": "service", "doctor-service-memory-contract.test.ts": "service", "doctor.test.ts": "codex-integration", "download-cap-default.test.ts": "images", "download-connect-deadline-default.test.ts": "images", @@ -1452,7 +1453,7 @@ "update-desktop-owner.test.ts": "update", "update-supervised-recovery.test.ts": "update", "update-job.test.ts": "update", "update-recovery-host-free.test.ts": "update", "update-job-supervised-restart.test.ts": "update", "update-standalone.test.ts": "update", - "update-worker-launch.test.ts": "update", "update-notify.test.ts": "update", + "update-worker-launch.test.ts": "update", "update-notify.test.ts": "update", "update-npm-allow-scripts.test.ts": "update", "update-orchestrated-session.test.ts": "update", "update-orchestrated-entry.test.ts": "update", "update-orchestrated-apply.test.ts": "update", "update-orchestrated-arguments.test.ts": "update", "update-orchestrated-plan.test.ts": "update", "update-sync-mutex.test.ts": "update", "update-orchestrated-identity.test.ts": "update", "update-orchestrated-token.test.ts": "update", "update-npm-cache-preflight.test.ts": "update", "update-npm-invocation.test.ts": "update", "update-npm-timeout.test.ts": "update", "update-npm-supervisor-exit.test.ts": "update", "update-mise-launcher-target.test.ts": "update", "update-mise.test.ts": "update", "update-mise-node-runtime.test.ts": "update", "update-pnpm.test.ts": "update", diff --git a/tests/lib/bun-runtime-preflight.test.ts b/tests/lib/bun-runtime-preflight.test.ts index 0c7dea985ff..adea703eb0a 100644 --- a/tests/lib/bun-runtime-preflight.test.ts +++ b/tests/lib/bun-runtime-preflight.test.ts @@ -23,7 +23,9 @@ function refusal(run: () => void, reason: RuntimePreflightReason): void { expect(error).toBeInstanceOf(RuntimePreflightError); expect(error).toMatchObject({ code: "OCX_RUNTIME_PREFLIGHT_FAILED", reason }); expect((error as Error).message).toContain("OPENCODEX_BUN_PATH"); - expect((error as Error).message).toContain("npm install -g @bitkyc08/opencodex"); + // #8361: the repair command must permit bun's postinstall, or npm 12 leaves the + // bundled runtime as its placeholder and the retry fails the same way. + expect((error as Error).message).toContain("npm install -g --allow-scripts=bun @bitkyc08/opencodex"); } } function child(result: Partial>): typeof spawnSync { diff --git a/tests/service/doctor-bun-install.test.ts b/tests/service/doctor-bun-install.test.ts new file mode 100644 index 00000000000..0b054ef8bd0 --- /dev/null +++ b/tests/service/doctor-bun-install.test.ts @@ -0,0 +1,117 @@ +import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"; +import * as proxyLiveness from "../../src/server/proxy-liveness"; +import * as bunRuntime from "../../src/lib/bun-runtime"; +import * as installDetection from "../../src/update/install-detection.mjs"; +import { getDefaultConfig } from "../../src/config"; +import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runDoctor } from "../../src/cli/doctor"; +import { collectBundledBunDiagnostic, formatBundledBunDiagnosticLines } from "../../src/cli/bundled-bun-diagnostic"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { STORE_BUDGET_MS } from "../helpers/test-budget"; + +/** + * Issue #8361: npm 12 blocks lifecycle scripts by default, so `npm install -g` leaves the + * bundled `bun` dependency as its un-downloaded placeholder. The launcher then reports + * "the `bun` dependency is not installed" and only a reinstall that permits bun's own + * postinstall repairs it. Doctor must name that state and the exact command. + * + * The bundled runtime and package-manager detection are faked at their module seams so the + * case exercises `runDoctor`'s real call site rather than a helper in isolation. + */ +describe("doctor bundled Bun install guidance (#8361)", () => { + let home: string; + let codexHome: string; + let previousHome: string | undefined; + let previousCodexHome: string | undefined; + let previousExitCode: typeof process.exitCode; + let logged: string[]; + const restore: Array<() => void> = []; + + beforeEach(() => { + home = mkdtempSync(join(tmpdir(), "ocx-doctor-bun-install-")); + codexHome = join(home, "codex"); + mkdirSync(codexHome, { recursive: true }); + previousHome = process.env.OPENCODEX_HOME; + previousCodexHome = process.env.CODEX_HOME; + previousExitCode = process.exitCode; + process.env.OPENCODEX_HOME = home; + process.env.CODEX_HOME = codexHome; + writeFileSync(join(home, "config.json"), JSON.stringify({ ...getDefaultConfig(), port: 9, codexAutoStart: false })); + logged = []; + const log = spyOn(console, "log").mockImplementation((...args: unknown[]) => { logged.push(args.map(String).join(" ")); }); + restore.push(() => log.mockRestore()); + // Doctor's other sections probe upstream health; this fixture must stay offline. + const fetch = spyOn(globalThis, "fetch").mockImplementation(async () => new Response(null, { status: 503 })); + restore.push(() => fetch.mockRestore()); + const live = spyOn(proxyLiveness, "findLiveProxy").mockResolvedValue({ pid: null, port: 9, hostname: "127.0.0.1", source: "config" }); + restore.push(() => live.mockRestore()); + }); + + afterEach(() => { + for (const cleanup of restore.splice(0).reverse()) cleanup(); + process.exitCode = previousExitCode; + if (previousHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousHome; + if (previousCodexHome === undefined) delete process.env.CODEX_HOME; + else process.env.CODEX_HOME = previousCodexHome; + if (existsSync(home)) removeTreeWithRetry(home); + }); + + test("an npm install whose Bun postinstall was skipped prints the exact repair command", async () => { + // A skipped postinstall leaves no real binary (bundledBunPath returns null for the + // placeholder stub). The package layout still identifies npm as the owner. + const bundled = spyOn(bunRuntime, "bundledBunPath").mockReturnValue(null); + restore.push(() => bundled.mockRestore()); + const detection = spyOn(installDetection, "detectInstallOwnershipFromPath") + .mockReturnValue({ installer: "npm" }); + restore.push(() => detection.mockRestore()); + + await runDoctor([]); + const report = logged.join("\n"); + + expect(report).toContain("the `bun` dependency is not installed"); + expect(report).toContain("npm install -g --allow-scripts=bun @bitkyc08/opencodex"); + }, STORE_BUDGET_MS); + + test("a ready bundled runtime does not suggest reinstalling", async () => { + const bundled = spyOn(bunRuntime, "bundledBunPath").mockReturnValue("/opt/ocx/node_modules/bun/bin/bun"); + restore.push(() => bundled.mockRestore()); + + await runDoctor([]); + const report = logged.join("\n"); + + expect(report).toContain("Bundled Bun runtime"); + expect(report).not.toContain("the `bun` dependency is not installed"); + expect(report).not.toContain("npm install -g --allow-scripts=bun"); + }, STORE_BUDGET_MS); + + test("a source checkout prints no empty Bundled Bun runtime heading", async () => { + const bundled = spyOn(bunRuntime, "bundledBunPath").mockReturnValue(null); + restore.push(() => bundled.mockRestore()); + const detection = spyOn(installDetection, "detectInstallOwnershipFromPath") + .mockReturnValue({ installer: "source" }); + restore.push(() => detection.mockRestore()); + + await runDoctor([]); + + expect(logged.join("\n")).not.toContain("Bundled Bun runtime"); + }, STORE_BUDGET_MS); + + test("a Windows npm install stops the proxy before reinstalling, since a live bun.exe holds bin", () => { + // Windows fails the manual reinstall with EBUSY while the service's bun.exe is running. + // `ocx update` handles this itself; the printed instruction has to match what it does. + const diagnostic = collectBundledBunDiagnostic({ + platform: "win32", + bundledPath: () => null, + detectOwnership: () => ({ installer: "npm" }), + }); + const lines = formatBundledBunDiagnosticLines(diagnostic).join("\n"); + + expect(lines).toContain("the `bun` dependency is not installed"); + expect(lines.indexOf("ocx stop")).toBeLessThan(lines.indexOf("npm install -g --allow-scripts=bun")); + expect(lines).toContain("npm install -g --allow-scripts=bun @bitkyc08/opencodex"); + expect(lines).toContain("ocx service restart"); + }); +}); diff --git a/tests/update/update-job.test.ts b/tests/update/update-job.test.ts index fe4780a2aa4..962a42405f7 100644 --- a/tests/update/update-job.test.ts +++ b/tests/update/update-job.test.ts @@ -1836,7 +1836,8 @@ describe("GUI update execution decisions", () => { describe("immutable update target (WP160)", () => { test("a resolved version pins the install target instead of the movable tag", () => { expect(updateCommand("bun", "latest", "2.7.24").args).toEqual(["add", "-g", "@bitkyc08/opencodex@2.7.24"]); - expect(updateCommand("npm", "latest", "2.7.24").args).toEqual(["install", "-g", "@bitkyc08/opencodex@2.7.24"]); + // npm 12 blocks dependency lifecycle scripts by default; see update-npm-allow-scripts.test.ts. + expect(updateCommand("npm", "latest", "2.7.24").args).toEqual(["install", "-g", "--allow-scripts=bun", "@bitkyc08/opencodex@2.7.24"]); expect(updateCommandStr("bun", "latest", "2.7.24")).toContain("@bitkyc08/opencodex@2.7.24"); // Unknown version falls back to the tag (best-effort lane). expect(updateCommand("bun", "latest").args).toEqual(["add", "-g", "@bitkyc08/opencodex@latest"]); diff --git a/tests/update/update-npm-allow-scripts.test.ts b/tests/update/update-npm-allow-scripts.test.ts new file mode 100644 index 00000000000..11d577234a1 --- /dev/null +++ b/tests/update/update-npm-allow-scripts.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { updateCommand, updateCommandStr } from "../../src/update/index"; +import { repoPath } from "../helpers/repo-root"; + +/** + * Issue #8361: npm 12 blocks dependency lifecycle scripts by default, so an `ocx update` that + * ran a bare `npm install -g` removed the old files and installed a package whose bundled `bun` + * dependency never unpacked. The next `ocx` then failed with "the `bun` dependency is not + * installed". Every path that builds an npm install argument must approve exactly that one script. + * + * The flag is not conditional on the npm major: npm 8 through 11 all install with it on a global + * install (some npm 11 releases warn that it is unknown to the CLI config, then install). + * A version gate would add a failure mode that depends on which npm happened to be first on PATH. + */ +describe("npm update allows bun's postinstall (#8361)", () => { + test("the update command passes --allow-scripts=bun before the package spec", () => { + expect(updateCommand("npm", "latest", "2.7.24")).toEqual({ + bin: "npm", + args: ["install", "-g", "--allow-scripts=bun", "@bitkyc08/opencodex@2.7.24"], + }); + // The tag fallback (registry resolution unavailable) keeps the same approval. + expect(updateCommand("npm", "preview", null).args).toEqual([ + "install", "-g", "--allow-scripts=bun", "@bitkyc08/opencodex@preview", + ]); + expect(updateCommandStr("npm", "latest", "2.7.24")) + .toBe("npm install -g --allow-scripts=bun @bitkyc08/opencodex@2.7.24"); + }); + + test("the other managers keep their own build approval instead of npm's flag", () => { + expect(updateCommand("pnpm", "latest", "2.7.24").args).toEqual([ + "add", "-g", "--allow-build=bun", "@bitkyc08/opencodex@2.7.24", + ]); + expect(updateCommand("bun", "latest", "2.7.24").args).toEqual([ + "add", "-g", "@bitkyc08/opencodex@2.7.24", + ]); + }); + + test("the Node launcher's own npm reinstall carries the same approval", () => { + // The launcher is plain Node and runs before this module loads, so it repeats the argv. + // Source-pinned because loading it executes the CLI. + const launcher = readFileSync(repoPath("bin", "ocx.mjs"), "utf8"); + expect(launcher).toContain('["install", "-g", "--allow-scripts=bun", `${PKG}@${selector}`]'); + // The manual-update failure hint and the fatal runtime message name the same command. + expect(launcher).toContain('"npm install -g --allow-scripts=bun @bitkyc08/opencodex"'); + expect(launcher).toContain('["install", "-g", "--allow-scripts=bun", `${PKG}@${latest || tag}`]'); + }); +});