From c84f3bcd9d4e501f9dd583aed4bccbad0b024b5a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 30 Sep 2026 04:07:14 +0800 Subject: [PATCH 1/4] fix(macos): safeguard desktop rollback and use agent subdomains --- scripts/macos/catalog-validator.js | 2 +- scripts/macos/install.sh | 68 +++++++++++++++++++++++------- scripts/macos/launch-managed.sh | 4 +- scripts/macos/uninstall.sh | 17 +++++++- tests/macos/catalog_smoke.py | 4 +- tests/macos/smoke.sh | 59 +++++++++++++++++++++++++- 6 files changed, 130 insertions(+), 24 deletions(-) diff --git a/scripts/macos/catalog-validator.js b/scripts/macos/catalog-validator.js index bf9cce3..c740bda 100644 --- a/scripts/macos/catalog-validator.js +++ b/scripts/macos/catalog-validator.js @@ -121,7 +121,7 @@ function run(args) { 'CLAUDE_CODE_USE_FOUNDRY', 'CLAUDE_CODE_USE_ANTHROPIC_AWS', 'CLAUDE_CODE_USE_MANTLE'].forEach(function (key) { data.env[key] = '' }) - data.env.ANTHROPIC_BASE_URL = 'https://neuroapi.host/v1/claude-code' + data.env.ANTHROPIC_BASE_URL = 'https://claude.neuroapi.host' data.env.ANTHROPIC_MODEL = data.model // Keep first-request quota reservations bounded. Claude Code can continue // generation in another turn when a response reaches this limit. diff --git a/scripts/macos/install.sh b/scripts/macos/install.sh index 9cbb365..3f2b82d 100755 --- a/scripts/macos/install.sh +++ b/scripts/macos/install.sh @@ -55,13 +55,19 @@ if [[ -e "$DESKTOP_HASH_PATH" ]]; then printf 'Codex Desktop ownership state is incomplete. Existing settings were not changed.\n' >&2 exit 1 fi - IFS=' ' read -r desktop_state_version desktop_original_exists desktop_applied_hash <"$DESKTOP_HASH_PATH" - if [[ "$desktop_state_version" != 'v1' || ! "$desktop_original_exists" =~ ^[01]$ || + IFS=' ' read -r desktop_state_version desktop_original_exists desktop_applied_hash desktop_original_hash <"$DESKTOP_HASH_PATH" + if [[ ! "$desktop_state_version" =~ ^v[12]$ || ! "$desktop_original_exists" =~ ^[01]$ || ! "$desktop_applied_hash" =~ ^[a-f0-9]{64}$ || "$(/usr/bin/shasum -a 256 "$DESKTOP_CONFIG_PATH" | /usr/bin/awk '{print $1}')" != "$desktop_applied_hash" ]]; then printf 'Codex Desktop config changed since setup. Resolve it manually before reinstalling.\n' >&2 exit 1 fi + if [[ "$desktop_state_version" == 'v2' ]] && + { [[ ! "$desktop_original_hash" =~ ^[a-f0-9]{64}$ ]] || + [[ "$(/usr/bin/shasum -a 256 "$DESKTOP_ORIGINAL_PATH" | /usr/bin/awk '{print $1}')" != "$desktop_original_hash" ]]; }; then + printf 'Codex Desktop original backup changed since setup. Settings and credentials were kept.\n' >&2 + exit 1 + fi DESKTOP_OPT_IN=1 elif [[ -e "$DESKTOP_ORIGINAL_PATH" || -e "$DESKTOP_CATALOG_PATH" ]]; then printf 'Codex Desktop ownership state is incomplete. Existing settings were not changed.\n' >&2 @@ -143,27 +149,53 @@ DESKTOP_ROOT_WAS_PRESENT=0 PROMOTE_TARGETS=() PROMOTE_BACKUPS=() cleanup_stage() { - local status=$? i + local status=$? i rollback_failed=0 restore_tmp trap - EXIT set +e if [[ "$status" -ne 0 && "$COMMIT_STARTED" == '1' ]]; then for ((i=0; i<${#PROMOTE_TARGETS[@]}; i++)); do - rm -f -- "${PROMOTE_TARGETS[i]}.new.$$" + rm -f -- "${PROMOTE_TARGETS[i]}.new.$$" || rollback_failed=1 done for ((i=PROMOTE_COUNT-1; i>=0; i--)); do + # A user or another process may have edited a promoted file. Leave that + # state intact and keep both credentials plus all snapshots for recovery. + if ! cmp -s -- "${PROMOTE_SOURCES[i]}" "${PROMOTE_TARGETS[i]}"; then + rollback_failed=1 + continue + fi + if is_test_mode && [[ "${NEUROAPI_AGENTS_TEST_FAIL_ROLLBACK_AT:-}" == "$(basename -- "${PROMOTE_TARGETS[i]}")" ]]; then + rollback_failed=1 + continue + fi if [[ "${PROMOTE_BACKUPS[i]}" == 'present' ]]; then - cp -p -- "$STAGE_ROOT/rollback/$i" "${PROMOTE_TARGETS[i]}" + restore_tmp="${PROMOTE_TARGETS[i]}.rollback.$$" + if ! cp -p -- "$STAGE_ROOT/rollback/$i" "$restore_tmp" || + ! cmp -s -- "$STAGE_ROOT/rollback/$i" "$restore_tmp" || + ! mv -f -- "$restore_tmp" "${PROMOTE_TARGETS[i]}" || + ! cmp -s -- "$STAGE_ROOT/rollback/$i" "${PROMOTE_TARGETS[i]}"; then + rollback_failed=1 + rm -f -- "$restore_tmp" || true + fi else - rm -f -- "${PROMOTE_TARGETS[i]}" + if ! rm -f -- "${PROMOTE_TARGETS[i]}" || + [[ -e "${PROMOTE_TARGETS[i]}" || -L "${PROMOTE_TARGETS[i]}" ]]; then + rollback_failed=1 + fi fi done - if [[ "$STATE_ROOT_WAS_PRESENT" == '0' ]]; then - rm -rf -- "$STATE_ROOT" - elif ! marker_is_owned "$(state_marker_path)"; then - rmdir -- "$STATE_ROOT/bin" "$STATE_ROOT/config" 2>/dev/null || true + if [[ "$rollback_failed" == '0' ]]; then + if [[ "$STATE_ROOT_WAS_PRESENT" == '0' ]]; then + rm -rf -- "$STATE_ROOT" || rollback_failed=1 + elif ! marker_is_owned "$(state_marker_path)"; then + rmdir -- "$STATE_ROOT/bin" "$STATE_ROOT/config" 2>/dev/null || true + fi + if [[ "$DESKTOP_OPT_IN" == '1' && "$DESKTOP_ROOT_WAS_PRESENT" == '0' ]]; then + rmdir -- "$DESKTOP_CONFIG_ROOT" 2>/dev/null || true + fi fi - if [[ "$DESKTOP_OPT_IN" == '1' && "$DESKTOP_ROOT_WAS_PRESENT" == '0' ]]; then - rmdir -- "$DESKTOP_CONFIG_ROOT" 2>/dev/null || true + if [[ "$rollback_failed" == '1' ]]; then + printf 'NeuroAPI setup rollback is incomplete. Snapshots and the candidate Keychain credential were kept. Recovery files: %s\n' "$STAGE_ROOT" >&2 + exit "$status" fi fi if [[ "$CANDIDATE_OWNED" == '1' ]]; then @@ -231,6 +263,11 @@ if [[ "$DESKTOP_OPT_IN" == '1' ]]; then printf 'Codex Desktop config changed during setup; existing settings were preserved.\n' >&2 exit 1 fi + if [[ -e "$DESKTOP_HASH_PATH" && "$desktop_state_version" == 'v2' && + "$(/usr/bin/shasum -a 256 "$STAGE_ROOT/desktop/original.toml" | /usr/bin/awk '{print $1}')" != "$desktop_original_hash" ]]; then + printf 'Codex Desktop original backup changed during setup; existing settings were preserved.\n' >&2 + exit 1 + fi if ! "$STAGE_ROOT/bin/launch-managed.sh" codex --export-codex-catalog "$STAGE_ROOT/desktop"; then printf 'Не удалось проверить модели Codex Desktop для этого ключа. Настройки сохранены.\n' >&2 exit 1 @@ -245,7 +282,8 @@ if [[ "$DESKTOP_OPT_IN" == '1' ]]; then IFS=' ' read -r _ desktop_original_exists _ <"$DESKTOP_HASH_PATH" fi desktop_hash="$(/usr/bin/shasum -a 256 "$STAGE_ROOT/desktop/config.toml" | /usr/bin/awk '{print $1}')" - printf 'v1 %s %s\n' "$desktop_original_exists" "$desktop_hash" >"$STAGE_ROOT/desktop/state" + desktop_original_hash="$(/usr/bin/shasum -a 256 "$STAGE_ROOT/desktop/original.toml" | /usr/bin/awk '{print $1}')" + printf 'v2 %s %s %s\n' "$desktop_original_exists" "$desktop_hash" "$desktop_original_hash" >"$STAGE_ROOT/desktop/state" desktop_codex_bin="$(resolve_client_bin codex 0 158 0 || true)" if [[ -z "$desktop_codex_bin" ]] || ! client_version_at_least "$desktop_codex_bin" 0 158 0; then printf 'Для проверки Codex Desktop требуется Codex CLI 0.158.0 или новее.\n' >&2 @@ -277,7 +315,7 @@ browser_use = false [model_providers.neuroapi] name = "NeuroAPI" -base_url = "https://neuroapi.host/v1/codex" +base_url = "https://codex.neuroapi.host/v1" wire_api = "responses" supports_websockets = true @@ -295,7 +333,7 @@ STAGED_CLAUDE_SETTINGS="$STAGE_ROOT/config/claude-settings.json" /usr/bin/plutil -insert apiKeyHelper -string "$HELPER_PATH" "$STAGED_CLAUDE_SETTINGS" /usr/bin/plutil -insert env -dictionary "$STAGED_CLAUDE_SETTINGS" /usr/bin/plutil -insert env.ANTHROPIC_BASE_URL \ - -string 'https://neuroapi.host/v1/claude-code' \ + -string 'https://claude.neuroapi.host' \ "$STAGED_CLAUDE_SETTINGS" /usr/bin/plutil -convert json "$STAGED_CLAUDE_SETTINGS" chmod 600 "$STAGED_CLAUDE_SETTINGS" diff --git a/scripts/macos/launch-managed.sh b/scripts/macos/launch-managed.sh index db244cc..95dbcca 100755 --- a/scripts/macos/launch-managed.sh +++ b/scripts/macos/launch-managed.sh @@ -10,8 +10,8 @@ SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)" CLIENT="${1:-}" shift || true case "$CLIENT" in - codex) MIN_MAJOR=0; MIN_MINOR=158; MIN_PATCH=0; ENDPOINT='https://neuroapi.host/v1/codex/models' ;; - claude) MIN_MAJOR=2; MIN_MINOR=1; MIN_PATCH=284; ENDPOINT='https://neuroapi.host/v1/claude-code/client-settings' ;; + codex) MIN_MAJOR=0; MIN_MINOR=158; MIN_PATCH=0; ENDPOINT='https://codex.neuroapi.host/v1/models' ;; + claude) MIN_MAJOR=2; MIN_MINOR=1; MIN_PATCH=284; ENDPOINT='https://claude.neuroapi.host/client-settings' ;; *) printf 'Неизвестный клиент NeuroAPI.\n' >&2; exit 1 ;; esac diff --git a/scripts/macos/uninstall.sh b/scripts/macos/uninstall.sh index c494f1d..b70a4bd 100755 --- a/scripts/macos/uninstall.sh +++ b/scripts/macos/uninstall.sh @@ -62,15 +62,25 @@ if [[ -e "$DESKTOP_HASH_PATH" ]]; then printf 'Codex Desktop ownership state is incomplete; uninstall stopped.\n' >&2 exit 1 fi - IFS=' ' read -r desktop_state_version desktop_original_exists desktop_applied_hash <"$DESKTOP_HASH_PATH" - if [[ "$desktop_state_version" != 'v1' || ! "$desktop_original_exists" =~ ^[01]$ || + IFS=' ' read -r desktop_state_version desktop_original_exists desktop_applied_hash desktop_original_hash <"$DESKTOP_HASH_PATH" + if [[ "$desktop_state_version" == 'v1' ]]; then + printf 'Codex Desktop backup predates integrity tracking. Re-run setup to upgrade it before uninstalling; credentials were kept.\n' >&2 + exit 1 + fi + if [[ "$desktop_state_version" != 'v2' || ! "$desktop_original_exists" =~ ^[01]$ || ! "$desktop_applied_hash" =~ ^[a-f0-9]{64}$ || + ! "$desktop_original_hash" =~ ^[a-f0-9]{64}$ || "$(/usr/bin/shasum -a 256 "$DESKTOP_CONFIG_PATH" | /usr/bin/awk '{print $1}')" != "$desktop_applied_hash" ]]; then printf 'Codex Desktop config changed since setup. Restore the saved config manually or remove the NeuroAPI provider, then retry uninstall. Credentials were kept.\n' >&2 exit 1 fi + if [[ "$(/usr/bin/shasum -a 256 "$DESKTOP_ORIGINAL_PATH" | /usr/bin/awk '{print $1}')" != "$desktop_original_hash" ]]; then + printf 'Codex Desktop original backup changed since setup. Uninstall stopped; credentials were kept.\n' >&2 + exit 1 + fi if [[ "$desktop_original_exists" == '1' ]]; then cp -p -- "$DESKTOP_ORIGINAL_PATH" "$DESKTOP_CONFIG_PATH.new.$$" + cmp -s -- "$DESKTOP_ORIGINAL_PATH" "$DESKTOP_CONFIG_PATH.new.$$" mv -f -- "$DESKTOP_CONFIG_PATH.new.$$" "$DESKTOP_CONFIG_PATH" else rm -f -- "$DESKTOP_CONFIG_PATH" @@ -78,6 +88,9 @@ if [[ -e "$DESKTOP_HASH_PATH" ]]; then elif [[ -e "$DESKTOP_ORIGINAL_PATH" || -e "$DESKTOP_CATALOG_PATH" ]]; then printf 'Codex Desktop ownership state is incomplete; uninstall stopped.\n' >&2 exit 1 +elif [[ -f "$DESKTOP_CONFIG_PATH" ]] && /usr/bin/grep -Fq 'neuroapi_agents' "$DESKTOP_CONFIG_PATH"; then + printf 'Codex Desktop still references NeuroAPI but its ownership state is missing. Uninstall stopped; credentials were kept.\n' >&2 + exit 1 fi if marker_is_owned "$(keychain_marker_path)"; then diff --git a/tests/macos/catalog_smoke.py b/tests/macos/catalog_smoke.py index 28d913a..1b803c8 100644 --- a/tests/macos/catalog_smoke.py +++ b/tests/macos/catalog_smoke.py @@ -48,7 +48,7 @@ def claude_catalog(slug="claude-opus-5-5"): assert args[args.index('--max-time')+1] == '20' assert args[args.index('--max-filesize')+1] == '4194304' assert args[args.index('--proto')+1] == '=https' -assert args[-1] in ['https://neuroapi.host/v1/codex/models', 'https://neuroapi.host/v1/claude-code/client-settings'] +assert args[-1] in ['https://codex.neuroapi.host/v1/models', 'https://claude.neuroapi.host/client-settings'] pathlib.Path(os.environ['TEST_FETCH_MARKER']).write_text('fetch') if os.environ.get('TEST_FETCH_FAIL'): print('private upstream error') @@ -76,7 +76,7 @@ def claude_catalog(slug="claude-opus-5-5"): else: path = pathlib.Path(a[a.index('--settings') + 1]) payload = json.loads(path.read_text()) - assert payload['env']['ANTHROPIC_BASE_URL'] == 'https://neuroapi.host/v1/claude-code' + assert payload['env']['ANTHROPIC_BASE_URL'] == 'https://claude.neuroapi.host' assert payload['env']['CLAUDE_CODE_MAX_OUTPUT_TOKENS'] == '4096' assert 'apiKeyHelper' in payload and 'test-neuroapi-token' not in path.read_text() neutralized = ['ANTHROPIC_SMALL_FAST_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL', 'ANTHROPIC_DEFAULT_MODEL', diff --git a/tests/macos/smoke.sh b/tests/macos/smoke.sh index f24dd7a..199113c 100755 --- a/tests/macos/smoke.sh +++ b/tests/macos/smoke.sh @@ -156,8 +156,8 @@ config="$(cat)" [[ "$config" == *'Bearer '* ]] || exit 1 [[ "$config" != *'bad-rotation'* ]] || exit 22 case "${!#}" in - https://neuroapi.host/v1/codex/models) cat "$NEUROAPI_TEST_CODEX_CATALOG" ;; - https://neuroapi.host/v1/claude-code/client-settings) cat "$NEUROAPI_TEST_CLAUDE_CATALOG" ;; + https://codex.neuroapi.host/v1/models) cat "$NEUROAPI_TEST_CODEX_CATALOG" ;; + https://claude.neuroapi.host/client-settings) cat "$NEUROAPI_TEST_CLAUDE_CATALOG" ;; *) exit 1 ;; esac printf '\n200' @@ -247,6 +247,28 @@ model = "gpt-6-astra" apps = false EOF cp "$NEUROAPI_AGENTS_DESKTOP_CODEX_HOME/config.toml" "$TMP_ROOT/desktop-original.toml" +cp "$rotation_path" "$TMP_ROOT/desktop-pointer-before" +if PATH="$MOCK_CLIENT_BIN:$PATH" TMPDIR="$TMP_ROOT" \ + NEUROAPI_AGENTS_TEST_FAIL_COMMIT_AT=config.toml \ + NEUROAPI_AGENTS_TEST_FAIL_ROLLBACK_AT=keychain-service \ + /bin/bash "$REPO_ROOT/scripts/macos/install.sh" >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err"; then + printf 'Injected rollback failure was ignored.\n' >&2 + exit 1 +fi +recovery_stage="$(find "$TMP_ROOT" -maxdepth 1 -type d -name 'neuroapi-agents-stage.*' -print -quit)" +[[ -n "$recovery_stage" && -f "$recovery_stage/rollback/0" ]] +grep -Fq 'rollback is incomplete' "$TMP_ROOT/desktop.err" +grep -Fq "$recovery_stage" "$TMP_ROOT/desktop.err" +candidate_service="$(<"$rotation_path")" +[[ "$candidate_service" != "$(<"$TMP_ROOT/desktop-pointer-before")" ]] +[[ -f "$MOCK_KEYCHAIN_STATE/$candidate_service" ]] +[[ -f "$MOCK_KEYCHAIN_STATE/$(<"$TMP_ROOT/desktop-pointer-before")" ]] +[[ "$("$NEUROAPI_AGENTS_STATE_ROOT/bin/get-neuroapi-key.sh")" == 'test-neuroapi-token' ]] +cp "$TMP_ROOT/desktop-pointer-before" "$rotation_path" +"$MOCK_SECURITY" delete-generic-password -a "$(/usr/bin/id -un)" -s "$candidate_service" +rm -rf -- "$recovery_stage" +cmp "$TMP_ROOT/desktop-original.toml" "$NEUROAPI_AGENTS_DESKTOP_CODEX_HOME/config.toml" + if PATH="$MOCK_CLIENT_BIN:$PATH" NEUROAPI_AGENTS_TEST_FAIL_COMMIT_AT=config.toml \ /bin/bash "$REPO_ROOT/scripts/macos/install.sh" >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err"; then printf 'Injected Desktop commit failure was ignored.\n' >&2 @@ -272,6 +294,39 @@ cp "$desktop_config" "$TMP_ROOT/desktop-installed.toml" PATH="$MOCK_CLIENT_BIN:$PATH" /bin/bash "$REPO_ROOT/scripts/macos/install.sh" \ >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err" cmp "$TMP_ROOT/desktop-installed.toml" "$desktop_config" +desktop_state="$NEUROAPI_AGENTS_STATE_ROOT/config/codex-desktop-state" +desktop_original_backup="$NEUROAPI_AGENTS_STATE_ROOT/config/codex-desktop-original.toml" +grep -Eq '^v2 [01] [a-f0-9]{64} [a-f0-9]{64}$' "$desktop_state" +cp "$desktop_original_backup" "$TMP_ROOT/desktop-backup-before" +printf '\n# corrupted backup\n' >>"$desktop_original_backup" +if /bin/bash "$REPO_ROOT/scripts/macos/uninstall.sh" >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err"; then + printf 'Uninstall accepted a corrupted Desktop backup.\n' >&2 + exit 1 +fi +[[ -f "$NEUROAPI_AGENTS_STATE_ROOT/bin/get-neuroapi-key.sh" ]] +cmp "$TMP_ROOT/desktop-installed.toml" "$desktop_config" +cp "$TMP_ROOT/desktop-backup-before" "$desktop_original_backup" +IFS=' ' read -r _ desktop_original_exists desktop_applied_hash _ <"$desktop_state" +printf 'v1 %s %s\n' "$desktop_original_exists" "$desktop_applied_hash" >"$desktop_state" +if /bin/bash "$REPO_ROOT/scripts/macos/uninstall.sh" >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err"; then + printf 'Uninstall accepted an unverifiable v1 Desktop backup.\n' >&2 + exit 1 +fi +PATH="$MOCK_CLIENT_BIN:$PATH" /bin/bash "$REPO_ROOT/scripts/macos/install.sh" \ + >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err" +grep -Eq '^v2 [01] [a-f0-9]{64} [a-f0-9]{64}$' "$desktop_state" +cmp "$TMP_ROOT/desktop-installed.toml" "$desktop_config" +mv "$desktop_state" "$TMP_ROOT/missing-desktop-state" +mv "$desktop_original_backup" "$TMP_ROOT/missing-desktop-backup" +mv "$desktop_catalog" "$TMP_ROOT/missing-desktop-catalog" +if /bin/bash "$REPO_ROOT/scripts/macos/uninstall.sh" >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err"; then + printf 'Uninstall deleted credentials for a config with missing ownership state.\n' >&2 + exit 1 +fi +[[ -f "$NEUROAPI_AGENTS_STATE_ROOT/bin/get-neuroapi-key.sh" ]] +mv "$TMP_ROOT/missing-desktop-state" "$desktop_state" +mv "$TMP_ROOT/missing-desktop-backup" "$desktop_original_backup" +mv "$TMP_ROOT/missing-desktop-catalog" "$desktop_catalog" printf '\n# user changed this later\n' >>"$desktop_config" if /bin/bash "$REPO_ROOT/scripts/macos/uninstall.sh" >"$TMP_ROOT/desktop.out" 2>"$TMP_ROOT/desktop.err"; then printf 'Uninstall deleted credentials for a modified Desktop config.\n' >&2 From 65ca78dd4b3be6f0bf126e1c38e71810481a8b01 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 30 Sep 2026 04:07:16 +0800 Subject: [PATCH 2/4] fix(installer): use dedicated agent client domains --- README.en.md | 6 +++--- README.md | 6 +++--- docs/manual-setup.md | 6 +++--- docs/security.md | 4 ++-- docs/troubleshooting.md | 2 +- scripts/windows/common.ps1 | 5 +++-- scripts/windows/managed-catalog.ps1 | 6 +++--- scripts/windows/setup.ps1 | 4 ++-- tests/static/profile_contract.py | 2 +- tests/static/validate.ps1 | 2 +- tests/windows/managed-catalog.ps1 | 4 ++-- tests/windows/smoke.ps1 | 4 ++-- 12 files changed, 26 insertions(+), 25 deletions(-) diff --git a/README.en.md b/README.en.md index e5cfe8b..6465f73 100644 --- a/README.en.md +++ b/README.en.md @@ -6,7 +6,7 @@ Public, auditable guided setup for routing local Codex CLI and Claude Code sessi ## Release compatibility -This version configures `https://neuroapi.host/v1/codex` with `supports_websockets = true`, and `https://neuroapi.host/v1/claude-code` for Claude Code. The Codex profile disables hosted web search, multi-agent, goals, apps, and browser use because the current client includes these tools even in simple local tasks, while NeuroAPI does not guarantee their upstream execution. Local shell and file tools remain available. Setup checks both key-scoped catalogs without a paid generation. Verify HTTP/WebSocket Responses and Claude Messages/count_tokens after each server release. +This version configures `https://codex.neuroapi.host/v1` with `supports_websockets = true`, and `https://claude.neuroapi.host` for Claude Code. The Codex profile disables hosted web search, multi-agent, goals, apps, and browser use because the current client includes these tools even in simple local tasks, while NeuroAPI does not guarantee their upstream execution. Local shell and file tools remain available. Setup checks both key-scoped catalogs without a paid generation. Verify HTTP/WebSocket Responses and Claude Messages/count_tokens after each server release. An existing `CODEX_HOME` selects the profile directory without being modified. Keep its value consistent for setup, launch and uninstall. @@ -49,8 +49,8 @@ This prevents accidental plaintext disclosure. It does not protect a key from ma ## Verification -- In `codex-neuroapi`, run `/debug-config` and confirm the `neuroapi-host` profile and `https://neuroapi.host/v1/codex`. -- In `claude-neuroapi`, run `/status` and confirm `https://neuroapi.host/v1/claude-code` plus `apiKeyHelper`. +- In `codex-neuroapi`, run `/debug-config` and confirm the `neuroapi-host` profile and `https://codex.neuroapi.host/v1`. +- In `claude-neuroapi`, run `/status` and confirm `https://claude.neuroapi.host` plus `apiKeyHelper`. - For Codex Desktop, check a new local task, `https://codex.neuroapi.host/v1` in the user config, and the request in your NeuroAPI usage logs. Setup uses HTTP/SSE for this GUI integration. - Check current model IDs and pricing at [neuroapi.host/price](https://neuroapi.host/price). diff --git a/README.md b/README.md index e892fea..281d401 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ NeuroAPI — российский AI API-сервис: единый доступ ## Совместимость версии -Эта версия создаёт профиль Codex с `https://neuroapi.host/v1/codex` и `supports_websockets = true`, а профиль Claude Code — с `https://neuroapi.host/v1/claude-code`. Codex-профиль отключает hosted web search, multi-agent, goals, apps и browser use: эти инструменты новейший клиент отправляет даже в простых задачах, а NeuroAPI пока не гарантирует их провайдерское исполнение. Локальные команды, чтение и редактирование файлов работают. Установщик проверяет доступ ключа к обоим каталогам без платной генерации. Генерацию через HTTP/WebSocket Responses и Claude Messages/count_tokens проверяйте после серверного релиза. +Эта версия создаёт профиль Codex с `https://codex.neuroapi.host/v1` и `supports_websockets = true`, а профиль Claude Code — с `https://claude.neuroapi.host`. Codex-профиль отключает hosted web search, multi-agent, goals, apps и browser use: эти инструменты новейший клиент отправляет даже в простых задачах, а NeuroAPI пока не гарантирует их провайдерское исполнение. Локальные команды, чтение и редактирование файлов работают. Установщик проверяет доступ ключа к обоим каталогам без платной генерации. Генерацию через HTTP/WebSocket Responses и Claude Messages/count_tokens проверяйте после серверного релиза. Уже заданный `CODEX_HOME` учитывается для профиля и не изменяется; сохраняйте одинаковое значение при установке, запуске и удалении. @@ -111,13 +111,13 @@ Codex CLI: 1. Запустите `codex-neuroapi`. 2. Выполните `/debug-config`. -3. Проверьте профиль `neuroapi-host`, provider `neuroapi` и `https://neuroapi.host/v1/codex`. +3. Проверьте профиль `neuroapi-host`, provider `neuroapi` и `https://codex.neuroapi.host/v1`. Claude Code: 1. Запустите `claude-neuroapi`. 2. Выполните `/status`. -3. Проверьте base URL `https://neuroapi.host/v1/claude-code` и credential source `apiKeyHelper`. +3. Проверьте base URL `https://claude.neuroapi.host` и credential source `apiKeyHelper`. При каждом запуске launcher получает актуальный список для вашего обычного ключа NeuroAPI. Codex использует отдельный каталог, Claude Code — настроенное меню; фиксированных моделей в установщике нет. Проверенные минимумы: Codex 0.158.0 и Claude Code 2.1.284. Для Claude Code установщик задаёт начальный лимит вывода 4096 токенов, чтобы запросы с большим стандартным лимитом не резервировали избыточную квоту. При ошибке обновления или пустом списке запуск останавливается, не возвращаясь к старым моделям. Подробности и ограничения managed-политик: [ручная настройка](docs/manual-setup.md). diff --git a/docs/manual-setup.md b/docs/manual-setup.md index 7ee343a..8431133 100644 --- a/docs/manual-setup.md +++ b/docs/manual-setup.md @@ -25,7 +25,7 @@ browser_use = false [model_providers.neuroapi] name = "NeuroAPI" -base_url = "https://neuroapi.host/v1/codex" +base_url = "https://codex.neuroapi.host/v1" wire_api = "responses" supports_websockets = true @@ -53,7 +53,7 @@ Project `.codex/config.toml` не подходит для provider/auth redirect ## HTTP/SSE для диагностики -Если соединение WebSocket блокируется вашей сетью, в секции `[model_providers.neuroapi]` созданного профиля замените `supports_websockets = true` на `supports_websockets = false`. Base URL остаётся `https://neuroapi.host/v1/codex`, auth helper и key storage не меняются. При повторном запуске setup управляемый профиль снова получит настройку по умолчанию `true`. +Если соединение WebSocket блокируется вашей сетью, в секции `[model_providers.neuroapi]` созданного профиля замените `supports_websockets = true` на `supports_websockets = false`. Base URL остаётся `https://codex.neuroapi.host/v1`, auth helper и key storage не меняются. При повторном запуске setup управляемый профиль снова получит настройку по умолчанию `true`. Если сервер ещё не предоставляет `/v1/codex`, не распространяйте эту версию установщика: сначала требуется согласованный серверный выпуск. Возврат к общему `/v1` не решает несовпадение формата каталога при command-backed auth. @@ -66,7 +66,7 @@ Project `.codex/config.toml` не подходит для provider/auth redirect "$schema": "https://json.schemastore.org/claude-code-settings.json", "apiKeyHelper": "/absolute/path/to/installer-owned-helper", "env": { - "ANTHROPIC_BASE_URL": "https://neuroapi.host/v1/claude-code" + "ANTHROPIC_BASE_URL": "https://claude.neuroapi.host" } } ``` diff --git a/docs/security.md b/docs/security.md index af1cc5c..e8dc683 100644 --- a/docs/security.md +++ b/docs/security.md @@ -45,8 +45,8 @@ macOS: Setup и uninstall не вызывают NeuroAPI. Каждый launcher сначала получает актуальный каталог через HTTPS с сохранённым ключом: фиксированный адрес, запрет redirects, ограничение времени и размера ответа. Ключ остаётся в памяти/анонимных pipes, не передаётся в argv и не сохраняется в каталоге. Отражение ключа в ответе проверяется до записи, включая JSON escaping. Произвольные hooks, команды и настройки сервера не принимаются. -- Codex custom provider: `https://neuroapi.host/v1/codex`; -- Claude Code gateway: `https://neuroapi.host/v1/claude-code`. +- Codex custom provider: `https://codex.neuroapi.host/v1`; +- Claude Code gateway: `https://claude.neuroapi.host`. Проверенные данные записываются в приватный отдельный каталог на время одного запуска, затем удаляются. Параллельные запуски не делят изменяемый snapshot. Ошибка загрузки не включает старый каталог. Чужие provider/auth overrides нейтрализуются только в дочернем процессе и его локальных settings; настройки пользователя на диске не переписываются. Host-managed provider mode отклоняется, а не обходится. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index bed3861..321651c 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -20,7 +20,7 @@ macOS: используйте полный путь `~/.local/bin/codex-neuroapi - profile `neuroapi-host`; - provider `neuroapi`; -- base URL `https://neuroapi.host/v1/codex`; +- base URL `https://codex.neuroapi.host/v1`; - `wire_api = "responses"`; - `supports_websockets = true` (или `false` для диагностики HTTP/SSE). - `web_search = "disabled"` и `[features]` с `multi_agent = false`, `goals = false`, `apps = false`, `browser_use = false`: без них Codex 0.158.0 может включить неподдерживаемые hosted/namespace-инструменты в обычный запрос к файлу и получить `effective_request_unsupported`. diff --git a/scripts/windows/common.ps1 b/scripts/windows/common.ps1 index 937d525..61bd0d5 100644 --- a/scripts/windows/common.ps1 +++ b/scripts/windows/common.ps1 @@ -265,9 +265,10 @@ function Assert-NeuroAPIKeyCatalogs { throw 'Неверный формат ключа NeuroAPI.' } foreach ($client in @('codex', 'claude')) { - $endpoint = if ($client -eq 'codex') { 'https://neuroapi.host/v1/codex/models' } else { 'https://neuroapi.host/v1/claude-code/client-settings' } + $endpoint = if ($client -eq 'codex') { 'https://codex.neuroapi.host/v1/models' } else { 'https://claude.neuroapi.host/client-settings' } + $allowedHost = if ($client -eq 'codex') { 'codex.neuroapi.host' } else { 'claude.neuroapi.host' } try { - $result = Get-NeuroAPIHTTPSContent -Url $endpoint -AllowedHosts @('neuroapi.host') -MaxBytes (2 * 1024 * 1024) -Bearer $credential -TimeoutSeconds 20 + $result = Get-NeuroAPIHTTPSContent -Url $endpoint -AllowedHosts @($allowedHost) -MaxBytes (2 * 1024 * 1024) -Bearer $credential -TimeoutSeconds 20 } catch { throw 'Не удалось проверить ключ: каталог NeuroAPI временно недоступен. Попробуйте позже.' } diff --git a/scripts/windows/managed-catalog.ps1 b/scripts/windows/managed-catalog.ps1 index 86001d7..599e760 100644 --- a/scripts/windows/managed-catalog.ps1 +++ b/scripts/windows/managed-catalog.ps1 @@ -65,9 +65,9 @@ function New-NeuroAPICatalogClient { function Get-NeuroAPICatalogJson { param([ValidateSet('codex', 'claude')][string]$Client, [string]$SecretPath) $endpoint = if ($Client -eq 'codex') { - 'https://neuroapi.host/v1/codex/models' + 'https://codex.neuroapi.host/v1/models' } else { - 'https://neuroapi.host/v1/claude-code/client-settings' + 'https://claude.neuroapi.host/client-settings' } $http = $null; $request = $null; $response = $null; $stream = $null; $buffer = $null; $timeout = $null $credential = $null @@ -255,7 +255,7 @@ function ConvertFrom-NeuroAPICatalog { } if (-not $seen.Contains($model)) { throw 'Default model hidden from picker' } $safeEnv = [ordered]@{ - ANTHROPIC_BASE_URL = 'https://neuroapi.host/v1/claude-code' + ANTHROPIC_BASE_URL = 'https://claude.neuroapi.host' ANTHROPIC_MODEL = $model CLAUDE_CODE_MAX_OUTPUT_TOKENS = '4096' } diff --git a/scripts/windows/setup.ps1 b/scripts/windows/setup.ps1 index 6acc0a6..8d82ac7 100644 --- a/scripts/windows/setup.ps1 +++ b/scripts/windows/setup.ps1 @@ -194,7 +194,7 @@ browser_use = false [model_providers.neuroapi] name = "NeuroAPI" -base_url = "https://neuroapi.host/v1/codex" +base_url = "https://codex.neuroapi.host/v1" wire_api = "responses" supports_websockets = true @@ -213,7 +213,7 @@ refresh_interval_ms = 300000 '$schema' = 'https://json.schemastore.org/claude-code-settings.json' apiKeyHelper = $helperCommand env = [ordered]@{ - ANTHROPIC_BASE_URL = 'https://neuroapi.host/v1/claude-code' + ANTHROPIC_BASE_URL = 'https://claude.neuroapi.host' } } | ConvertTo-Json -Depth 5 Write-Utf8NoBom -Path (Join-Path $stageRoot 'claude-settings.json') -Content $claudeSettings diff --git a/tests/static/profile_contract.py b/tests/static/profile_contract.py index 79c8868..8384683 100644 --- a/tests/static/profile_contract.py +++ b/tests/static/profile_contract.py @@ -16,7 +16,7 @@ "browser_use": False, } provider = profile["model_providers"]["neuroapi"] -assert provider["base_url"] == "https://neuroapi.host/v1/codex" +assert provider["base_url"] == "https://codex.neuroapi.host/v1" assert provider["wire_api"] == "responses" assert provider["supports_websockets"] is True assert set(provider) == {"name", "base_url", "wire_api", "supports_websockets", "auth"} diff --git a/tests/static/validate.ps1 b/tests/static/validate.ps1 index e646350..9a60216 100644 --- a/tests/static/validate.ps1 +++ b/tests/static/validate.ps1 @@ -85,7 +85,7 @@ Assert-NotMatches $macSetup '\|\s*"\$SECURITY_BIN"\s+add-generic-password' ` 'macOS setup must not pipe a key into the Keychain command.' foreach ($setup in @($windowsSetup, $macSetup)) { - Assert-Contains $setup 'base_url = "https://neuroapi.host/v1/codex"' ` + Assert-Contains $setup 'base_url = "https://codex.neuroapi.host/v1"' ` 'Codex must use the profile-specific API and catalog.' Assert-Contains $setup 'supports_websockets = true' ` 'Codex must enable the supported Responses WebSocket transport.' diff --git a/tests/windows/managed-catalog.ps1 b/tests/windows/managed-catalog.ps1 index aae7a33..5352946 100644 --- a/tests/windows/managed-catalog.ps1 +++ b/tests/windows/managed-catalog.ps1 @@ -140,7 +140,7 @@ foreach ($client in @('codex', 'claude')) { $wire = Get-NeuroAPICatalogJson $client 'unused-secret-path' Assert-Catalog ($wire -ceq $script:mockHttp.Body) 'Catalog response changed.' Assert-Catalog ($script:mockHttp.Authorization -ceq 'Bearer test-neuroapi-token') 'Authorization missing.' - $expected = if ($client -eq 'codex') { 'https://neuroapi.host/v1/codex/models' } else { 'https://neuroapi.host/v1/claude-code/client-settings' } + $expected = if ($client -eq 'codex') { 'https://codex.neuroapi.host/v1/models' } else { 'https://claude.neuroapi.host/client-settings' } Assert-Catalog ($script:mockHttp.Uri -ceq $expected) 'Wrong catalog endpoint.' } foreach ($status in @(301, 302, 401, 403, 429, 503)) { @@ -240,7 +240,7 @@ try { Assert-Catalog ([string]::IsNullOrEmpty($env:ANTHROPIC_SMALL_FAST_MODEL)) 'Inherited fast model overrides catalog.' Assert-Catalog ([string]::IsNullOrEmpty($env:ANTHROPIC_DEFAULT_FABLE_MODEL)) 'Missing family inherited.' Assert-Catalog ($env:ANTHROPIC_DEFAULT_HAIKU_MODEL -ceq 'claude-haiku-4-5') 'Concrete family missing.' - Assert-Catalog ($env:ANTHROPIC_BASE_URL -ceq 'https://neuroapi.host/v1/claude-code') 'Claude endpoint missing.' + Assert-Catalog ($env:ANTHROPIC_BASE_URL -ceq 'https://claude.neuroapi.host') 'Claude endpoint missing.' } $script:NeuroAPIChildExitCode = 37 } diff --git a/tests/windows/smoke.ps1 b/tests/windows/smoke.ps1 index f68612d..ffa1e9d 100644 --- a/tests/windows/smoke.ps1 +++ b/tests/windows/smoke.ps1 @@ -60,7 +60,7 @@ try { Assert-True (Test-Path -LiteralPath $helperPath) 'Credential helper was not installed.' Assert-True (Test-Path -LiteralPath $settingsPath) 'Claude settings were not created.' $profileBefore = Get-Content -LiteralPath $profilePath -Raw - Assert-True ($profileBefore -match '(?m)^base_url = "https://neuroapi.host/v1/codex"\r?$') 'Wrong Codex base URL.' + Assert-True ($profileBefore -match '(?m)^base_url = "https://codex.neuroapi.host/v1"\r?$') 'Wrong Codex base URL.' Assert-True ($profileBefore -match '(?m)^supports_websockets = true\r?$') 'Codex WebSocket support is missing.' Assert-True ($profileBefore -notmatch 'test-neuroapi-token|experimental_bearer_token|env_key|http_headers') 'Credential leaked into Codex configuration.' Assert-True ((Get-Content -LiteralPath $settingsPath -Raw) -notmatch 'test-neuroapi-token') 'Credential leaked into Claude configuration.' @@ -89,7 +89,7 @@ try { Remove-Item -LiteralPath $privateSnapshot -Force $settings = Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json - Assert-True ($settings.env.ANTHROPIC_BASE_URL -ceq 'https://neuroapi.host/v1/claude-code') 'Wrong Claude base URL.' + Assert-True ($settings.env.ANTHROPIC_BASE_URL -ceq 'https://claude.neuroapi.host') 'Wrong Claude base URL.' Assert-True (-not ($settings.env.PSObject.Properties.Name -contains 'ANTHROPIC_MODEL')) 'A bundled model must not override fresh catalog defaults.' Assert-True ($profileBefore -notmatch '(?m)^model =') 'Codex default must come from the fresh catalog.' foreach ($launcherFile in @('common.ps1', 'managed-catalog.ps1', 'launch-neuroapi.ps1')) { From 18c28d00538a67a9716dee61476d158d6d6dfe45 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 30 Sep 2026 04:09:29 +0800 Subject: [PATCH 3/4] docs(installer): align setup checks with subdomains --- README.en.md | 2 +- README.md | 4 ++-- docs/manual-setup.md | 6 +++--- docs/security.md | 2 +- docs/troubleshooting.md | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.en.md b/README.en.md index 6465f73..80fda8e 100644 --- a/README.en.md +++ b/README.en.md @@ -10,7 +10,7 @@ This version configures `https://codex.neuroapi.host/v1` with `supports_websocke An existing `CODEX_HOME` selects the profile directory without being modified. Keep its value consistent for setup, launch and uninstall. -Use a current Codex release whose `--help` describes `--profile` as loading `.config.toml`. Update older clients that expect `[profiles.name]` in the main configuration. For WebSocket troubleshooting, temporarily set `supports_websockets = false` in the generated profile, keeping `/v1/codex` and its credential helper. +Use a current Codex release whose `--help` describes `--profile` as loading `.config.toml`. Update older clients that expect `[profiles.name]` in the main configuration. For WebSocket troubleshooting, temporarily set `supports_websockets = false` in the generated profile, keeping `https://codex.neuroapi.host/v1` and its credential helper. ## Quick start diff --git a/README.md b/README.md index 281d401..6e9bc3f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ NeuroAPI — российский AI API-сервис: единый доступ Уже заданный `CODEX_HOME` учитывается для профиля и не изменяется; сохраняйте одинаковое значение при установке, запуске и удалении. -Нужен актуальный Codex с отдельными profile-файлами: `codex --help` должен описывать `--profile` как загрузку `.config.toml`. Старые версии с `[profiles.name]` в общем конфиге обновите перед установкой. Для диагностики WebSocket можно временно поставить `supports_websockets = false` в созданном профиле, сохранив `/v1/codex` и credential helper. Подробнее — [решение проблем](docs/troubleshooting.md). +Нужен актуальный Codex с отдельными profile-файлами: `codex --help` должен описывать `--profile` как загрузку `.config.toml`. Старые версии с `[profiles.name]` в общем конфиге обновите перед установкой. Для диагностики WebSocket можно временно поставить `supports_websockets = false` в созданном профиле, сохранив `https://codex.neuroapi.host/v1` и credential helper. Подробнее — [решение проблем](docs/troubleshooting.md). ## Установка в один запуск @@ -74,7 +74,7 @@ bash setup-macos.command | Codex Desktop по согласию | пользовательский `config.toml`, DPAPI helper, каталог доступных моделей | пользовательский `config.toml`, Keychain helper, каталог доступных моделей | | Существующий `config.toml` | резервная копия и атомарное изменение только по согласию; конфликт останавливает установку | резервная копия и атомарное изменение только по согласию; конфликт останавливает установку | -Установщик отправляет ключ только в NeuroAPI для проверки доступных моделей; платной генерации при настройке нет. При запуске `codex-neuroapi` или `claude-neuroapi` запускатель вновь получает актуальный каталог с `https://neuroapi.host`, затем клиент использует API при ваших запросах. +Установщик отправляет ключ только в NeuroAPI для проверки доступных моделей; платной генерации при настройке нет. При запуске `codex-neuroapi` или `claude-neuroapi` запускатель вновь получает актуальный каталог с соответствующего субдомена NeuroAPI, затем клиент использует API при ваших запросах. ## Почему ключ не лежит в конфиге diff --git a/docs/manual-setup.md b/docs/manual-setup.md index 8431133..ce766d1 100644 --- a/docs/manual-setup.md +++ b/docs/manual-setup.md @@ -39,7 +39,7 @@ refresh_interval_ms = 300000 Codex 0.158.0 по умолчанию добавляет к каждому запросу hosted `web_search` и namespace-инструмент для multi-agent, даже при локальном чтении файла. NeuroAPI не объявляет эти инструменты как поддерживаемые для Codex-профиля: они требуют отдельного провайдерского контракта и тарификации. Профиль отключает только эти возможности, а чтение, правка и запуск команд остаются доступны. Возвращать их вручную в профиле можно лишь после отдельной проверки поддержки сервером. -Запуск: `codex-neuroapi`. Перед каждым запуском launcher получает `/v1/codex/models` с обычным ключом NeuroAPI, проверяет ответ и передаёт приватный файл через `model_catalog_json` вместе с доступной моделью по умолчанию. Файл удаляется после завершения клиента. Проверка: `/debug-config` и `/model`. +Запуск: `codex-neuroapi`. Перед каждым запуском launcher получает `https://codex.neuroapi.host/v1/models` с обычным ключом NeuroAPI, проверяет ответ и передаёт приватный файл через `model_catalog_json` вместе с доступной моделью по умолчанию. Файл удаляется после завершения клиента. Проверка: `/debug-config` и `/model`. Прямой `codex --profile neuroapi-host` пропускает этот механизм: command-auth discovery может подмешать встроенные модели. При ручной настройке без launcher можно задать собственный проверенный `model_catalog_json`; поддерживать его актуальность тогда нужно самостоятельно. @@ -55,11 +55,11 @@ Project `.codex/config.toml` не подходит для provider/auth redirect Если соединение WebSocket блокируется вашей сетью, в секции `[model_providers.neuroapi]` созданного профиля замените `supports_websockets = true` на `supports_websockets = false`. Base URL остаётся `https://codex.neuroapi.host/v1`, auth helper и key storage не меняются. При повторном запуске setup управляемый профиль снова получит настройку по умолчанию `true`. -Если сервер ещё не предоставляет `/v1/codex`, не распространяйте эту версию установщика: сначала требуется согласованный серверный выпуск. Возврат к общему `/v1` не решает несовпадение формата каталога при command-backed auth. +Если сервер ещё не предоставляет `https://codex.neuroapi.host/v1/models` и `/v1/responses` на том же субдомене, не распространяйте эту версию установщика: сначала требуется согласованный серверный выпуск. Общий `/v1` возвращает другой формат каталога при command-backed auth. ## Claude Code -Нужен Claude Code 2.1.284 или новее. Существующий `~/.claude/settings.json` не меняется. Launcher получает `/v1/claude-code/client-settings`, проверяет разрешённые поля данных, добавляет локальный `apiKeyHelper` и передаёт приватный JSON через `claude --settings `. +Нужен Claude Code 2.1.284 или новее. Существующий `~/.claude/settings.json` не меняется. Launcher получает `https://claude.neuroapi.host/client-settings`, проверяет разрешённые поля данных, добавляет локальный `apiKeyHelper` и передаёт приватный JSON через `claude --settings `. ```json { diff --git a/docs/security.md b/docs/security.md index e8dc683..10a4515 100644 --- a/docs/security.md +++ b/docs/security.md @@ -43,7 +43,7 @@ macOS: ## Сетевое поведение -Setup и uninstall не вызывают NeuroAPI. Каждый launcher сначала получает актуальный каталог через HTTPS с сохранённым ключом: фиксированный адрес, запрет redirects, ограничение времени и размера ответа. Ключ остаётся в памяти/анонимных pipes, не передаётся в argv и не сохраняется в каталоге. Отражение ключа в ответе проверяется до записи, включая JSON escaping. Произвольные hooks, команды и настройки сервера не принимаются. +Uninstall не вызывает NeuroAPI. Setup проверяет ключ через каталоги, а каждый launcher сначала получает актуальный каталог через HTTPS с сохранённым ключом: фиксированный адрес, запрет redirects, ограничение времени и размера ответа. Ключ остаётся в памяти/анонимных pipes, не передаётся в argv и не сохраняется в каталоге. Отражение ключа в ответе проверяется до записи, включая JSON escaping. Произвольные hooks, команды и настройки сервера не принимаются. - Codex custom provider: `https://codex.neuroapi.host/v1`; - Claude Code gateway: `https://claude.neuroapi.host`. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 321651c..9ffe403 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -29,9 +29,9 @@ macOS: используйте полный путь `~/.local/bin/codex-neuroapi ## WebSocket не подключается -В `[model_providers.neuroapi]` файла `neuroapi-host.config.toml` временно установите `supports_websockets = false`, оставив base URL `/v1/codex`. Перезапустите `codex-neuroapi` и проверьте короткий запрос по HTTP/SSE. Не переносите ключ в TOML и не меняйте helper. Повторная установка восстановит `true`. +В `[model_providers.neuroapi]` файла `neuroapi-host.config.toml` временно установите `supports_websockets = false`, оставив base URL `https://codex.neuroapi.host/v1`. Перезапустите `codex-neuroapi` и проверьте короткий запрос по HTTP/SSE. Не переносите ключ в TOML и не меняйте helper. Повторная установка восстановит `true`. -`404` на `/v1/codex/models` или `/v1/codex/responses` может означать, что серверное обновление ещё не опубликовано. Эта версия установщика должна распространяться только после проверки обоих адресов и WebSocket на сервере. Один fallback на HTTP не создаёт отсутствующий endpoint. +`404` на `https://codex.neuroapi.host/v1/models` или `/v1/responses` этого же субдомена может означать, что серверное обновление ещё не опубликовано. Эта версия установщика должна распространяться только после проверки обоих адресов и WebSocket на сервере. Один fallback на HTTP не создаёт отсутствующий endpoint. ## Codex сообщает, что профиль не найден @@ -55,7 +55,7 @@ macOS может показать системный запрос доступа ## Каталог не загружается или список пуст -Проверьте ключ, соединение и ограничения моделей/тарифа. Для этой версии нужны `/v1/codex/models` и `/v1/claude-code/client-settings`; обновление установщика само по себе не создаёт их на сервере. Ответы `401`, `403`, redirects, слишком большой/неправильный JSON и пустой список не заменяются локальным старым каталогом. Серверное обновление должно быть выпущено до распространения установщика. +Проверьте ключ, соединение и ограничения моделей/тарифа. Для этой версии нужны `https://codex.neuroapi.host/v1/models` и `https://claude.neuroapi.host/client-settings`; обновление установщика само по себе не создаёт их на сервере. Ответы `401`, `403`, redirects, слишком большой/неправильный JSON и пустой список не заменяются локальным старым каталогом. Серверное обновление должно быть выпущено до распространения установщика. ## В меню всё ещё есть другие модели From 18a35b68e319e475cecb6dc3a1ce172f9056ff91 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 30 Sep 2026 04:12:27 +0800 Subject: [PATCH 4/4] test(installer): match dedicated catalog host in first-run mock --- tests/windows/first-run.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/windows/first-run.ps1 b/tests/windows/first-run.ps1 index 72c9a9e..410c667 100644 --- a/tests/windows/first-run.ps1 +++ b/tests/windows/first-run.ps1 @@ -69,7 +69,9 @@ try { param([string]$Url, [string[]]$AllowedHosts, [long]$MaxBytes, [string]$Bearer, [string]$OutputPath, [int]$TimeoutSeconds) if ($Bearer -cne 'dummy-secret') { throw 'Wrong test credential' } $script:CatalogCalls++ - $client = if ($Url -like '*/codex/*') { 'codex' } else { 'claude' } + $hostName = ([Uri]$Url).Host + if ($AllowedHosts.Count -ne 1 -or $AllowedHosts[0] -cne $hostName) { throw 'Catalog host allowlist mismatch' } + $client = if ($hostName -ceq 'codex.neuroapi.host') { 'codex' } else { 'claude' } return [pscustomobject]@{ Status = $script:CatalogStatus; Bytes = [Text.Encoding]::UTF8.GetBytes($script:CatalogBodies[$client]) } } Assert-NeuroAPIKeyCatalogs -SecureKey $secure