@remodex/rmx 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +91 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +584 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-CZqebSPQ.css +1 -0
- package/gui/dist/assets/index-CkETtt7P.js +71 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
- package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +2 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +118 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic.ts +1205 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +83 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/command-code.ts +453 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +139 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1235 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +59 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +604 -0
- package/src/adapters/cursor/protobuf-events.ts +735 -0
- package/src/adapters/cursor/protobuf-request.ts +719 -0
- package/src/adapters/cursor/request-builder.ts +280 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +621 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +59 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +520 -0
- package/src/adapters/google-antigravity-wire.ts +140 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +859 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +104 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1924 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1265 -0
- package/src/adapters/openai-responses.ts +1309 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/android-remote/assets.ts +218 -0
- package/src/android-remote/attachments.ts +168 -0
- package/src/android-remote/auth.ts +237 -0
- package/src/android-remote/cloudflare-provisioning.ts +409 -0
- package/src/android-remote/cloudflare-secret.ts +106 -0
- package/src/android-remote/cloudflare-tunnel.ts +488 -0
- package/src/android-remote/cloudflared.ts +286 -0
- package/src/android-remote/codex-app-server.ts +565 -0
- package/src/android-remote/desktop-history-page.ts +936 -0
- package/src/android-remote/desktop-ipc.ts +3643 -0
- package/src/android-remote/desktop-ownership-store.ts +98 -0
- package/src/android-remote/desktop-project-registration.ts +129 -0
- package/src/android-remote/desktop-session-stream.ts +1110 -0
- package/src/android-remote/desktop-workspace-state.ts +443 -0
- package/src/android-remote/file-change-parser.ts +112 -0
- package/src/android-remote/gateway.ts +9108 -0
- package/src/android-remote/mutation-store.ts +299 -0
- package/src/android-remote/projection.ts +1780 -0
- package/src/android-remote/queued-turn-store.ts +248 -0
- package/src/android-remote/session-command-recovery.ts +1384 -0
- package/src/android-remote/store.ts +466 -0
- package/src/android-remote/thread-reconciliation.ts +133 -0
- package/src/android-remote/thread-source-paths.ts +307 -0
- package/src/android-remote/thread-stream.ts +546 -0
- package/src/android-remote/turn-activity.ts +235 -0
- package/src/android-remote/user-message-identity.ts +94 -0
- package/src/bridge.ts +1793 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +267 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +601 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +519 -0
- package/src/claude/model-info.ts +154 -0
- package/src/claude/outbound.ts +898 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +296 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +476 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +297 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +211 -0
- package/src/cli/claude.ts +302 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +124 -0
- package/src/cli/config-command.ts +183 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/desktop-first-run.ts +25 -0
- package/src/cli/doctor.ts +1022 -0
- package/src/cli/export-command.ts +201 -0
- package/src/cli/help.ts +370 -0
- package/src/cli/index.ts +1565 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +225 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +35 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/models-runtime.ts +224 -0
- package/src/cli/models.ts +340 -0
- package/src/cli/observe.ts +170 -0
- package/src/cli/opencode.ts +587 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +476 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/system-command.ts +196 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +205 -0
- package/src/cli/v2.ts +200 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1109 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +40 -0
- package/src/codex/admission.ts +263 -0
- package/src/codex/app-server-processes.ts +799 -0
- package/src/codex/auth-api.ts +2098 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +480 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +471 -0
- package/src/codex/catalog/bundled.ts +533 -0
- package/src/codex/catalog/effort.ts +432 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +287 -0
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +503 -0
- package/src/codex/catalog/provider-fetch.ts +2267 -0
- package/src/codex/catalog/sync.ts +1606 -0
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +15 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +580 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-client-processes.ts +521 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1091 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +347 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +115 -0
- package/src/codex/history-provider.ts +1075 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +204 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +257 -0
- package/src/codex/inject.ts +1857 -0
- package/src/codex/injected-marker.ts +79 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +105 -0
- package/src/codex/journal.ts +172 -0
- package/src/codex/main-account-cache.ts +56 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/model-cache.ts +267 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +348 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +691 -0
- package/src/codex/paths.ts +78 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +426 -0
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/provider-adoption.ts +242 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +494 -0
- package/src/codex/refresh.ts +60 -0
- package/src/codex/routing.ts +1855 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +1215 -0
- package/src/codex/subagent-defaults.ts +557 -0
- package/src/codex/subagent-model-fallback.ts +560 -0
- package/src/codex/sync.ts +238 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +374 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +44 -0
- package/src/combos/request.ts +64 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +392 -0
- package/src/config.ts +3270 -0
- package/src/generated/model-metadata.ts +144 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +540 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +127 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +922 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +108 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +242 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +184 -0
- package/src/lib/bun-stream-caps.ts +127 -0
- package/src/lib/config-ownership.ts +438 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +182 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-environment.ts +470 -0
- package/src/lib/provider-outbound.ts +203 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +510 -0
- package/src/lib/remodex-home.ts +616 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +178 -0
- package/src/lib/shadow-call.ts +54 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +98 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +366 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +508 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +817 -0
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +402 -0
- package/src/model-sources.ts +73 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +231 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +443 -0
- package/src/oauth/index.ts +1280 -0
- package/src/oauth/key-providers.ts +128 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +655 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +317 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/command-code-efforts.ts +85 -0
- package/src/providers/context-cap.ts +73 -0
- package/src/providers/derive.ts +451 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +361 -0
- package/src/providers/openai-sidecar.ts +235 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +83 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +179 -0
- package/src/providers/quota.ts +1942 -0
- package/src/providers/reasoning-capabilities.ts +336 -0
- package/src/providers/registry.ts +2375 -0
- package/src/providers/slug-codec.ts +74 -0
- package/src/providers/xai-transport.ts +149 -0
- package/src/reasoning-effort.ts +243 -0
- package/src/responses/compaction.ts +124 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +714 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +106 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +431 -0
- package/src/responses/state.ts +1039 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +802 -0
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +205 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +424 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/adapter-resolve.ts +83 -0
- package/src/server/auth-cors.ts +606 -0
- package/src/server/chat-completions.ts +379 -0
- package/src/server/claude-messages.ts +980 -0
- package/src/server/effort-policy.ts +251 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +485 -0
- package/src/server/index.ts +1633 -0
- package/src/server/lifecycle.ts +482 -0
- package/src/server/live.ts +609 -0
- package/src/server/management/agent-settings-routes.ts +1180 -0
- package/src/server/management/android-remote-routes.ts +390 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +244 -0
- package/src/server/management/config-routes.ts +602 -0
- package/src/server/management/context.ts +88 -0
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +516 -0
- package/src/server/management/model-routes.ts +519 -0
- package/src/server/management/model-rows.ts +143 -0
- package/src/server/management/native-integration-routes.ts +781 -0
- package/src/server/management/oauth-account-routes.ts +573 -0
- package/src/server/management/provider-routes.ts +781 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +384 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +433 -0
- package/src/server/management/system-routes.ts +141 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +269 -0
- package/src/server/management-auth.ts +353 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +326 -0
- package/src/server/proxy-stop.ts +92 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1179 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1072 -0
- package/src/server/responses/collaboration.ts +409 -0
- package/src/server/responses/compact.ts +710 -0
- package/src/server/responses/core.ts +3561 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +171 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +152 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +272 -0
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses.ts +10 -0
- package/src/server/search.ts +181 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +119 -0
- package/src/server/system-env.ts +418 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +470 -0
- package/src/service-manager-probe.ts +824 -0
- package/src/service.ts +3011 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +364 -0
- package/src/tray/windows.ts +738 -0
- package/src/types.ts +1531 -0
- package/src/update/badge.ts +72 -0
- package/src/update/desktop-release.ts +1620 -0
- package/src/update/index.ts +402 -0
- package/src/update/job.ts +1906 -0
- package/src/update/notify.ts +261 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +283 -0
- package/src/usage/log.ts +695 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +127 -0
- package/src/vision/index.ts +558 -0
- package/src/vision/reasoning.ts +55 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +791 -0
- package/src/web-search/parse.ts +235 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getConfigDir } from "../config";
|
|
5
|
+
import { resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
|
|
6
|
+
import { PROXY_MARKER, defaultAuthDetectDeps, detectClaudeAuth, ownAdmissionTokens } from "../claude/auth-detect";
|
|
7
|
+
import { resolveClaudeAuthMode } from "../claude/auth-mode";
|
|
8
|
+
import type { OcxConfig } from "../types";
|
|
9
|
+
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Does the Remodex dummy marker belong in the system environment?
|
|
13
|
+
*
|
|
14
|
+
* Keyed on the SAME resolver `rmx claude` uses, so an auto config with no Claude auth
|
|
15
|
+
* also reaches plain `claude` launches — before this, auto-absent users got nothing
|
|
16
|
+
* from auto-connect and the feature looked broken for exactly the people it helps
|
|
17
|
+
* (devlog 260726_claude_auth_auto/035).
|
|
18
|
+
*
|
|
19
|
+
* NOTE this is a SNAPSHOT: the file only changes when this runs (proxy start, `ocx
|
|
20
|
+
* ensure`, or a settings save). `rmx claude` re-resolves live on every launch.
|
|
21
|
+
*/
|
|
22
|
+
function systemEnvMarkerMode(config: OcxConfig): "proxy" | "subscription" {
|
|
23
|
+
return resolveClaudeAuthMode(config, detectClaudeAuth(defaultAuthDetectDeps(process.env, ownAdmissionTokens(config)))).markerMode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Shell-hook env file: written on inject, sourced by the shell hook in .zshrc.
|
|
28
|
+
// This works for ALL new shells immediately, unlike launchctl setenv which only
|
|
29
|
+
// reaches processes launched directly by launchd (not Terminal.app children).
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export function getShellEnvFilePath(): string {
|
|
33
|
+
return join(getConfigDir(), "claude-env.sh");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function shellValue(value: string): string {
|
|
37
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeShellEnvFile(port: number, config: OcxConfig, modelEnv: Record<string, string> = {}, auto?: AutoContextMode): void {
|
|
41
|
+
const lines = [
|
|
42
|
+
`# Generated by Remodex — do not edit manually`,
|
|
43
|
+
`export ANTHROPIC_BASE_URL=${shellValue(`http://127.0.0.1:${port}`)}`,
|
|
44
|
+
`export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=${shellValue("1")}`,
|
|
45
|
+
];
|
|
46
|
+
// New lever keys are CONDITIONAL exports (audit 139 R2#1): a value the user already
|
|
47
|
+
// exported in their shell wins even though launchctl knows nothing about it.
|
|
48
|
+
const conditional = (name: string, value: string) =>
|
|
49
|
+
`[ -z "\${${name}+x}" ] && export ${name}=${shellValue(value)}`;
|
|
50
|
+
if (config.apiKeys?.length) {
|
|
51
|
+
lines.push(`export ANTHROPIC_AUTH_TOKEN=${shellValue(config.apiKeys[0].key)}`);
|
|
52
|
+
} else if (systemEnvMarkerMode(config) === "proxy") {
|
|
53
|
+
lines.push(conditional("ANTHROPIC_AUTH_TOKEN", PROXY_MARKER));
|
|
54
|
+
}
|
|
55
|
+
// Model slots (default + tiers + legacy small-fast) with [1m] applied (devlog 260712 B2).
|
|
56
|
+
if (modelEnv.ANTHROPIC_MODEL) {
|
|
57
|
+
lines.push(`export ANTHROPIC_MODEL=${shellValue(modelEnv.ANTHROPIC_MODEL)}`);
|
|
58
|
+
} else if (config.claudeCode?.model) {
|
|
59
|
+
lines.push(`export ANTHROPIC_MODEL=${shellValue(config.claudeCode.model)}`);
|
|
60
|
+
}
|
|
61
|
+
for (const [name, value] of Object.entries(modelEnv)) {
|
|
62
|
+
if (name === "ANTHROPIC_MODEL") continue;
|
|
63
|
+
lines.push(conditional(name, value));
|
|
64
|
+
}
|
|
65
|
+
const maxCtx = config.claudeCode?.maxContextTokens;
|
|
66
|
+
if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) {
|
|
67
|
+
lines.push(conditional("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx))));
|
|
68
|
+
lines.push(conditional("DISABLE_COMPACT", "1"));
|
|
69
|
+
}
|
|
70
|
+
// Auto-context (devlog 260712 020): same contract as `rmx claude` / launchctl.
|
|
71
|
+
const autoShell = auto ?? resolveAutoContext(config.claudeCode);
|
|
72
|
+
if (autoShell.enabled) lines.push(conditional("CLAUDE_CODE_AUTO_COMPACT_WINDOW", String(autoShell.compactWindow)));
|
|
73
|
+
if (config.claudeCode?.alwaysEnableEffort === true) {
|
|
74
|
+
lines.push(conditional("CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", "1"));
|
|
75
|
+
}
|
|
76
|
+
const shellEnvPath = getShellEnvFilePath();
|
|
77
|
+
recordOwnedConfigPath(getConfigDir(), shellEnvPath);
|
|
78
|
+
mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });
|
|
79
|
+
writeFileSync(shellEnvPath, lines.join("\n") + "\n", { encoding: "utf8", mode: 0o600 });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function removeShellEnvFile(): void {
|
|
83
|
+
try { unlinkSync(getShellEnvFilePath()); } catch { /* already gone */ }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// .zshrc hook auto-install: adds a one-liner that sources claude-env.sh.
|
|
88
|
+
// Idempotent — skips if the hook line already exists.
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
const SHELL_HOOK_MARKER = "# Remodex claude-env hook";
|
|
92
|
+
const LEGACY_SHELL_HOOK_MARKER = "# opencodex claude-env hook";
|
|
93
|
+
const SHELL_HOOK_LINE = `${SHELL_HOOK_MARKER}\n[ -f ~/.remodex/claude-env.sh ] && source ~/.remodex/claude-env.sh`;
|
|
94
|
+
|
|
95
|
+
export function installShellHook(): { installed: boolean; reason?: string } {
|
|
96
|
+
if (process.platform !== "darwin") return { installed: false, reason: "not macOS" };
|
|
97
|
+
const home = process.env.HOME;
|
|
98
|
+
if (!home) return { installed: false, reason: "no HOME" };
|
|
99
|
+
const zshrcPath = join(home, ".zshrc");
|
|
100
|
+
try {
|
|
101
|
+
let content = "";
|
|
102
|
+
try { content = readFileSync(zshrcPath, "utf8"); } catch { /* file doesn't exist yet */ }
|
|
103
|
+
if (content.includes(SHELL_HOOK_MARKER) || content.includes(LEGACY_SHELL_HOOK_MARKER)) {
|
|
104
|
+
return { installed: false, reason: "already installed" };
|
|
105
|
+
}
|
|
106
|
+
const addition = `\n${SHELL_HOOK_LINE}\n`;
|
|
107
|
+
writeFileSync(zshrcPath, content + addition, { encoding: "utf8", mode: 0o644 });
|
|
108
|
+
return { installed: true };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return { installed: false, reason: `write failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function uninstallShellHook(): { removed: boolean; reason?: string } {
|
|
115
|
+
if (process.platform !== "darwin") return { removed: false, reason: "not macOS" };
|
|
116
|
+
const home = process.env.HOME;
|
|
117
|
+
if (!home) return { removed: false, reason: "no HOME" };
|
|
118
|
+
const zshrcPath = join(home, ".zshrc");
|
|
119
|
+
try {
|
|
120
|
+
const content = readFileSync(zshrcPath, "utf8");
|
|
121
|
+
if (!content.includes(SHELL_HOOK_MARKER) && !content.includes(LEGACY_SHELL_HOOK_MARKER)) {
|
|
122
|
+
return { removed: false, reason: "not installed" };
|
|
123
|
+
}
|
|
124
|
+
// Remove the hook block (marker line + source line + surrounding newlines)
|
|
125
|
+
const cleaned = content.replace(/\n?# (?:Remodex|opencodex) claude-env hook\n\[.*claude-env\.sh.*\n?/g, "\n");
|
|
126
|
+
writeFileSync(zshrcPath, cleaned, { encoding: "utf8", mode: 0o644 });
|
|
127
|
+
return { removed: true };
|
|
128
|
+
} catch {
|
|
129
|
+
return { removed: false, reason: "read/write failed" };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const SYSTEM_ENV_NAMES = [
|
|
134
|
+
"ANTHROPIC_BASE_URL",
|
|
135
|
+
"CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
|
|
136
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
137
|
+
] as const;
|
|
138
|
+
|
|
139
|
+
const MANAGED_SYSTEM_ENV_NAMES = new Set<string>([
|
|
140
|
+
...SYSTEM_ENV_NAMES,
|
|
141
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
142
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
143
|
+
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
|
144
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
145
|
+
"ANTHROPIC_SMALL_FAST_MODEL",
|
|
146
|
+
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
|
147
|
+
"DISABLE_COMPACT",
|
|
148
|
+
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
|
149
|
+
"CLAUDE_CODE_ALWAYS_ENABLE_EFFORT",
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
interface SystemEnvTracking {
|
|
153
|
+
pid: number;
|
|
154
|
+
port: number;
|
|
155
|
+
injectedAt: string;
|
|
156
|
+
/** Keys that were actually set by injection (revert only unsets these). */
|
|
157
|
+
injectedKeys?: string[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type SystemEnvResult = { injected: boolean; reason?: string };
|
|
161
|
+
type RevertResult = { reverted: boolean; reason?: string };
|
|
162
|
+
type CleanupResult = { cleaned: boolean; reason?: string };
|
|
163
|
+
|
|
164
|
+
export function getSystemEnvTrackingPath(): string {
|
|
165
|
+
return join(getConfigDir(), "system-env-port");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function launchctlGetenv(name: string): string | undefined {
|
|
169
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) return undefined;
|
|
170
|
+
try {
|
|
171
|
+
const value = execFileSync("/bin/launchctl", ["getenv", name], { encoding: "utf8" }).trim();
|
|
172
|
+
return value || undefined;
|
|
173
|
+
} catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function readTracking(): SystemEnvTracking | undefined {
|
|
179
|
+
try {
|
|
180
|
+
const tracking = JSON.parse(readFileSync(getSystemEnvTrackingPath(), "utf8")) as Partial<SystemEnvTracking>;
|
|
181
|
+
if (!Number.isInteger(tracking.port) || typeof tracking.pid !== "number" || typeof tracking.injectedAt !== "string") {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const injectedKeys = Array.isArray(tracking.injectedKeys)
|
|
185
|
+
? [...new Set(tracking.injectedKeys.filter(
|
|
186
|
+
(name): name is string => typeof name === "string" && MANAGED_SYSTEM_ENV_NAMES.has(name),
|
|
187
|
+
))]
|
|
188
|
+
: undefined;
|
|
189
|
+
return { ...tracking, injectedKeys } as SystemEnvTracking;
|
|
190
|
+
} catch {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function setLaunchctlEnv(name: string, value: string): void {
|
|
196
|
+
execFileSync("/bin/launchctl", ["setenv", name, value]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function unsetLaunchctlEnv(name: string): void {
|
|
200
|
+
execFileSync("/bin/launchctl", ["unsetenv", name]);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function ownedBaseUrl(port: number): string {
|
|
204
|
+
return `http://127.0.0.1:${port}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function writeTracking(port: number, injectedKeys: string[]): void {
|
|
208
|
+
recordOwnedConfigPath(getConfigDir(), getSystemEnvTrackingPath());
|
|
209
|
+
mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });
|
|
210
|
+
writeFileSync(getSystemEnvTrackingPath(), JSON.stringify({
|
|
211
|
+
pid: process.pid,
|
|
212
|
+
port,
|
|
213
|
+
injectedAt: new Date().toISOString(),
|
|
214
|
+
injectedKeys,
|
|
215
|
+
}), { encoding: "utf8", mode: 0o600 });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function rollbackInjectedKeys(port: number, injectedKeys: string[]): void {
|
|
219
|
+
const rollbackFailed: string[] = [];
|
|
220
|
+
for (const name of [...injectedKeys].reverse()) {
|
|
221
|
+
try {
|
|
222
|
+
unsetLaunchctlEnv(name);
|
|
223
|
+
} catch {
|
|
224
|
+
rollbackFailed.unshift(name);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (rollbackFailed.length > 0) {
|
|
229
|
+
writeTracking(port, rollbackFailed);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try { unlinkSync(getSystemEnvTrackingPath()); } catch { /* already gone */ }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* In-process effective model-env (default + tier slots, [1m] applied) under the shared
|
|
238
|
+
* 3s bound (audit R4#3). Returns {} on timeout/failure so injection degrades safely.
|
|
239
|
+
*/
|
|
240
|
+
async function computeEffectiveModelEnv(config: OcxConfig, auto?: AutoContextMode): Promise<{ modelEnv: Record<string, string>; windows: Record<string, number> }> {
|
|
241
|
+
const { boundedContextWindows, buildClaudeContextWindows, effectiveModelEnv } = await import("../claude/context-windows");
|
|
242
|
+
const windows = await boundedContextWindows(async () => {
|
|
243
|
+
const { gatherRoutedModels, visibleNativeSlugs } = await import("../codex/catalog");
|
|
244
|
+
try {
|
|
245
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config));
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error && typeof error === "object" && (error as { code?: unknown }).code === "catalog_busy") {
|
|
248
|
+
return buildClaudeContextWindows([...visibleNativeSlugs(config)], []);
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
return { modelEnv: effectiveModelEnv(config.claudeCode, windows ?? {}, auto), windows: windows ?? {} };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function injectSystemEnv(port: number, config: OcxConfig): Promise<SystemEnvResult> {
|
|
257
|
+
if (process.platform !== "darwin") return { injected: false, reason: "not macOS" };
|
|
258
|
+
if (config.claudeCode?.enabled === false) return { injected: false, reason: "claude disabled" };
|
|
259
|
+
|
|
260
|
+
// Default OFF — only inject when explicitly enabled by the user.
|
|
261
|
+
if (config.claudeCode?.systemEnv !== true) return { injected: false, reason: "systemEnv disabled" };
|
|
262
|
+
|
|
263
|
+
await cleanStaleSystemEnv();
|
|
264
|
+
|
|
265
|
+
const currentBaseUrl = launchctlGetenv("ANTHROPIC_BASE_URL");
|
|
266
|
+
if (currentBaseUrl && !/^http:\/\/127\.0\.0\.1:\d+$/.test(currentBaseUrl)) {
|
|
267
|
+
return { injected: false, reason: "user has custom ANTHROPIC_BASE_URL" };
|
|
268
|
+
}
|
|
269
|
+
// After stale cleanup, if a tracking file still exists with a DIFFERENT port,
|
|
270
|
+
// another live instance owns the env — don't overwrite it.
|
|
271
|
+
const existingTracking = readTracking();
|
|
272
|
+
if (existingTracking && existingTracking.port !== port) {
|
|
273
|
+
return { injected: false, reason: `another instance owns env (port ${existingTracking.port})` };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const injectedKeys: string[] = existingTracking
|
|
277
|
+
? [...(existingTracking.injectedKeys ?? SYSTEM_ENV_NAMES)]
|
|
278
|
+
: [];
|
|
279
|
+
const inject = (name: string, value: string) => {
|
|
280
|
+
setLaunchctlEnv(name, value);
|
|
281
|
+
if (!injectedKeys.includes(name)) injectedKeys.push(name);
|
|
282
|
+
writeTracking(port, injectedKeys);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
inject("ANTHROPIC_BASE_URL", ownedBaseUrl(port));
|
|
287
|
+
inject("CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY", "1");
|
|
288
|
+
if (config.apiKeys?.length) {
|
|
289
|
+
inject("ANTHROPIC_AUTH_TOKEN", config.apiKeys[0].key);
|
|
290
|
+
} else if (systemEnvMarkerMode(config) === "proxy" && launchctlGetenv("ANTHROPIC_AUTH_TOKEN") === undefined) {
|
|
291
|
+
inject("ANTHROPIC_AUTH_TOKEN", PROXY_MARKER);
|
|
292
|
+
} else if (systemEnvMarkerMode(config) !== "proxy"
|
|
293
|
+
&& injectedKeys.includes("ANTHROPIC_AUTH_TOKEN")
|
|
294
|
+
&& launchctlGetenv("ANTHROPIC_AUTH_TOKEN") === PROXY_MARKER) {
|
|
295
|
+
// Subscription switch-back (devlog 260720_claude_authmode_persist): remove ONLY
|
|
296
|
+
// the Remodex-owned dummy token so a launchd-started Claude regains its own
|
|
297
|
+
// claude.ai OAuth. User-set tokens (not tracked in injectedKeys, or carrying a
|
|
298
|
+
// different value) are never touched.
|
|
299
|
+
unsetLaunchctlEnv("ANTHROPIC_AUTH_TOKEN");
|
|
300
|
+
const dummyIdx = injectedKeys.indexOf("ANTHROPIC_AUTH_TOKEN");
|
|
301
|
+
if (dummyIdx >= 0) injectedKeys.splice(dummyIdx, 1);
|
|
302
|
+
writeTracking(port, injectedKeys);
|
|
303
|
+
}
|
|
304
|
+
// Lever keys (devlog 136 B6): user-wins — skip any key the user already set in the
|
|
305
|
+
// launchd domain, and track ONLY the keys we actually injected so revert cannot
|
|
306
|
+
// delete a pre-existing user value (audit 139 #3).
|
|
307
|
+
const injectLever = (name: string, value: string) => {
|
|
308
|
+
if (launchctlGetenv(name) !== undefined) return;
|
|
309
|
+
inject(name, value);
|
|
310
|
+
};
|
|
311
|
+
// Model slots (default + tier defaults + legacy small-fast) with [1m] auto-marking
|
|
312
|
+
// (devlog 260712 B2, audit R2#3/R4#3): in-process context-window computation under
|
|
313
|
+
// the same 3s bound; on timeout the tier keys are simply not injected this run.
|
|
314
|
+
// Auto-context: a user-owned launchd value drives the marking predicate so the
|
|
315
|
+
// marker and threshold never separate (audit 021 #2); injectLever's user-wins
|
|
316
|
+
// check below keeps that value untouched.
|
|
317
|
+
const userAutoCompact = launchctlGetenv("CLAUDE_CODE_AUTO_COMPACT_WINDOW");
|
|
318
|
+
const auto = resolveAutoContext(config.claudeCode, userAutoCompact);
|
|
319
|
+
const { modelEnv, windows } = await computeEffectiveModelEnv(config, auto);
|
|
320
|
+
for (const [name, value] of Object.entries(modelEnv)) {
|
|
321
|
+
if (name === "ANTHROPIC_MODEL") continue; // legacy slot handled by shell file only (back-compat)
|
|
322
|
+
injectLever(name, value);
|
|
323
|
+
}
|
|
324
|
+
const maxCtx = config.claudeCode?.maxContextTokens;
|
|
325
|
+
if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) {
|
|
326
|
+
injectLever("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx)));
|
|
327
|
+
injectLever("DISABLE_COMPACT", "1");
|
|
328
|
+
}
|
|
329
|
+
// Auto-context (devlog 260712 020): user-wins lever, inert when maxContextTokens set.
|
|
330
|
+
if (auto.enabled) injectLever("CLAUDE_CODE_AUTO_COMPACT_WINDOW", String(auto.compactWindow));
|
|
331
|
+
if (config.claudeCode?.alwaysEnableEffort === true) {
|
|
332
|
+
injectLever("CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", "1");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Shell-hook env file: works for new shells in already-running Terminal.app.
|
|
336
|
+
writeShellEnvFile(port, config, modelEnv, auto);
|
|
337
|
+
|
|
338
|
+
// Gateway-model cache pre-write (devlog 030): plain `claude` sessions read the
|
|
339
|
+
// picker list from ~/.claude/cache/gateway-models.json and cannot refresh it
|
|
340
|
+
// without a token — keep it in sync with this proxy's /v1/models. Best-effort.
|
|
341
|
+
try {
|
|
342
|
+
const { refreshGatewayModelCacheFromProxy } = await import("../claude/gateway-cache");
|
|
343
|
+
await refreshGatewayModelCacheFromProxy(port);
|
|
344
|
+
} catch { /* best-effort */ }
|
|
345
|
+
|
|
346
|
+
// Roster agent definitions (devlog 070): same launch-time sync for plain `claude`.
|
|
347
|
+
// Reuses the window map computed above (audit 071 #5 — no second acquisition).
|
|
348
|
+
try {
|
|
349
|
+
const { injectClaudeAgentDefs } = await import("../claude/agents-inject");
|
|
350
|
+
injectClaudeAgentDefs(config, windows);
|
|
351
|
+
} catch { /* best-effort */ }
|
|
352
|
+
|
|
353
|
+
writeTracking(port, injectedKeys);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
rollbackInjectedKeys(port, injectedKeys);
|
|
356
|
+
removeShellEnvFile();
|
|
357
|
+
console.error("Failed to inject system environment; rolled back launchctl changes:", error);
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return { injected: true };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function applySystemEnvToggle(config: OcxConfig, port: number): Promise<SystemEnvResult | RevertResult> {
|
|
365
|
+
if (config.claudeCode?.systemEnv === true) return injectSystemEnv(port, config);
|
|
366
|
+
return revertSystemEnv();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function revertSystemEnv(): RevertResult {
|
|
370
|
+
if (process.platform !== "darwin") return { reverted: false, reason: "not macOS" };
|
|
371
|
+
|
|
372
|
+
const tracking = readTracking();
|
|
373
|
+
if (!tracking) return { reverted: false, reason: "no tracking file" };
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const tracksBaseUrl = tracking.injectedKeys?.includes("ANTHROPIC_BASE_URL") ?? true;
|
|
377
|
+
if (tracksBaseUrl && launchctlGetenv("ANTHROPIC_BASE_URL") !== ownedBaseUrl(tracking.port)) {
|
|
378
|
+
return { reverted: false, reason: "ownership mismatch" };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Only unset keys that were actually injected (preserves pre-existing user tokens).
|
|
382
|
+
const keysToUnset = tracking.injectedKeys ?? SYSTEM_ENV_NAMES as unknown as string[];
|
|
383
|
+
for (const name of keysToUnset) {
|
|
384
|
+
try {
|
|
385
|
+
unsetLaunchctlEnv(name);
|
|
386
|
+
} catch {
|
|
387
|
+
// Continue removing the remaining variables during shutdown.
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
removeShellEnvFile();
|
|
391
|
+
try {
|
|
392
|
+
unlinkSync(getSystemEnvTrackingPath());
|
|
393
|
+
} catch {
|
|
394
|
+
// The environment was reverted even if the tracking file disappeared concurrently.
|
|
395
|
+
}
|
|
396
|
+
return { reverted: true };
|
|
397
|
+
} catch {
|
|
398
|
+
return { reverted: false, reason: "revert failed" };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function cleanStaleSystemEnv(): Promise<CleanupResult> {
|
|
403
|
+
const tracking = readTracking();
|
|
404
|
+
if (!tracking) return { cleaned: false, reason: "no tracking file" };
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const response = await fetch(`${ownedBaseUrl(tracking.port)}/healthz`, {
|
|
408
|
+
signal: AbortSignal.timeout(1_000),
|
|
409
|
+
});
|
|
410
|
+
if (response.ok) return { cleaned: false, reason: "proxy still alive" };
|
|
411
|
+
} catch {
|
|
412
|
+
// A failed or timed-out health check means the tracked proxy is stale.
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const reverted = revertSystemEnv();
|
|
416
|
+
if (!reverted.reverted) return { cleaned: false, reason: reverted.reason };
|
|
417
|
+
return { cleaned: true };
|
|
418
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft SIGTERM-before-/F is NOT available on Windows (process.kill is TerminateProcess).
|
|
3
|
+
* Graceful drain is stopProxyGracefully(); this module only resets leftover TCBs after hard kill.
|
|
4
|
+
*/
|
|
5
|
+
import { dlopen, ptr, type Pointer } from "bun:ffi";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
export type TcpQuad = {
|
|
9
|
+
localAddr: string;
|
|
10
|
+
localPort: number;
|
|
11
|
+
remoteAddr: string;
|
|
12
|
+
remotePort: number;
|
|
13
|
+
state: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Parse netstat -ano rows whose local address uses `port`. Exported for tests. */
|
|
17
|
+
export function parseTcpQuadsForLocalPort(output: string, port: number): TcpQuad[] {
|
|
18
|
+
const rows: TcpQuad[] = [];
|
|
19
|
+
const portSuffix = `:${port}`;
|
|
20
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
21
|
+
const line = rawLine.trim();
|
|
22
|
+
if (!/^TCP\b/i.test(line)) continue;
|
|
23
|
+
const parts = line.split(/\s+/);
|
|
24
|
+
if (parts.length < 4) continue;
|
|
25
|
+
const local = parts[1]!;
|
|
26
|
+
const remote = parts[2]!;
|
|
27
|
+
const state = parts[3]!;
|
|
28
|
+
if (!local.endsWith(portSuffix) && !local.endsWith(`]:${port}`)) continue;
|
|
29
|
+
const localAddr = stripPort(local, port);
|
|
30
|
+
const remoteParsed = splitHostPort(remote);
|
|
31
|
+
if (!localAddr || !remoteParsed) continue;
|
|
32
|
+
rows.push({
|
|
33
|
+
localAddr,
|
|
34
|
+
localPort: port,
|
|
35
|
+
remoteAddr: remoteParsed.host,
|
|
36
|
+
remotePort: remoteParsed.port,
|
|
37
|
+
state,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return rows;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stripPort(addr: string, port: number): string | null {
|
|
44
|
+
const suffix = `:${port}`;
|
|
45
|
+
if (addr.endsWith(suffix)) return addr.slice(0, -suffix.length).replace(/^\[|\]$/g, "") || "0.0.0.0";
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function splitHostPort(addr: string): { host: string; port: number } | null {
|
|
50
|
+
if (addr === "0.0.0.0:0" || addr === "*:*" || addr === "[::]:0") {
|
|
51
|
+
return { host: "0.0.0.0", port: 0 };
|
|
52
|
+
}
|
|
53
|
+
const m = /^(?:\[([^\]]+)\]|([^:]+)):(\d+)$/.exec(addr);
|
|
54
|
+
if (!m) return null;
|
|
55
|
+
return { host: (m[1] ?? m[2] ?? "0.0.0.0").replace(/^::ffff:/i, ""), port: Number(m[3]) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ipv4ToWinUint32(addr: string): number | null {
|
|
59
|
+
const host = addr.replace(/^::ffff:/i, "");
|
|
60
|
+
// Refuse bare IPv6 — SetTcpEntry is IPv4-only; coercing "::"/"::1" to 0 would
|
|
61
|
+
// miss the real TCB and can hit an unrelated IPv4 wildcard row.
|
|
62
|
+
if (isBareIpv6Address(host)) return null;
|
|
63
|
+
if (host === "0.0.0.0" || host === "*") return 0;
|
|
64
|
+
const parts = host.split(".").map(Number);
|
|
65
|
+
if (parts.length !== 4 || parts.some(n => !Number.isInteger(n) || n < 0 || n > 255)) return null;
|
|
66
|
+
return (parts[0]! | (parts[1]! << 8) | (parts[2]! << 16) | (parts[3]! << 24)) >>> 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** True for bare IPv6 (including :: / ::1), false for dotted IPv4 and IPv4-mapped. */
|
|
70
|
+
export function isBareIpv6Address(addr: string): boolean {
|
|
71
|
+
const host = String(addr || "").replace(/^\[|\]$/g, "").replace(/^::ffff:/i, "");
|
|
72
|
+
if (!host) return false;
|
|
73
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) return false;
|
|
74
|
+
return host.includes(":");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type WindowsTcpDropResult = {
|
|
78
|
+
/** Successful SetTcpEntry(DELETE_TCB) calls for IPv4 rows. */
|
|
79
|
+
dropped: number;
|
|
80
|
+
/** IPv6 (or unparseable) rows skipped — never coerced into IPv4 wildcards. */
|
|
81
|
+
skippedIpv6: number;
|
|
82
|
+
/**
|
|
83
|
+
* SetTcpEntry returned ERROR_ACCESS_DENIED (5) or the UAC-non-elevated code (317).
|
|
84
|
+
* On a normal (non-admin) update worker this is expected — ghost LISTEN rows must
|
|
85
|
+
* clear by waiting for the OS, not by SetTcpEntry.
|
|
86
|
+
*/
|
|
87
|
+
accessDenied: number;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function htons(port: number): number {
|
|
91
|
+
return (((port & 0xff) << 8) | ((port >> 8) & 0xff)) >>> 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type SetTcpEntryFn = (row: Pointer) => number;
|
|
95
|
+
let setTcpEntryFn: SetTcpEntryFn | null | undefined;
|
|
96
|
+
|
|
97
|
+
function loadSetTcpEntry(): SetTcpEntryFn | null {
|
|
98
|
+
if (setTcpEntryFn !== undefined) return setTcpEntryFn;
|
|
99
|
+
if (process.platform !== "win32") {
|
|
100
|
+
setTcpEntryFn = null;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const lib = dlopen("iphlpapi.dll", {
|
|
105
|
+
SetTcpEntry: { args: ["ptr"], returns: "u32" },
|
|
106
|
+
});
|
|
107
|
+
setTcpEntryFn = (row: Pointer) => lib.symbols.SetTcpEntry(row) as number;
|
|
108
|
+
} catch {
|
|
109
|
+
setTcpEntryFn = null;
|
|
110
|
+
}
|
|
111
|
+
return setTcpEntryFn;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readNetstatAno(): string {
|
|
115
|
+
const netstat = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\netstat.exe`;
|
|
116
|
+
const cmd = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\cmd.exe`;
|
|
117
|
+
try {
|
|
118
|
+
return execFileSync(cmd, ["/d", "/c", `chcp 437>nul & "${netstat}" -ano -p tcp`], {
|
|
119
|
+
encoding: "utf-8",
|
|
120
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
121
|
+
timeout: 5000,
|
|
122
|
+
windowsHide: true,
|
|
123
|
+
});
|
|
124
|
+
} catch {
|
|
125
|
+
return execFileSync(netstat, ["-ano", "-p", "tcp"], {
|
|
126
|
+
encoding: "utf-8",
|
|
127
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
128
|
+
timeout: 4000,
|
|
129
|
+
windowsHide: true,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Force-delete IPv4 TCP rows bound to `localPort` via SetTcpEntry(DELETE_TCB).
|
|
136
|
+
* Does not kill foreign processes — only resets sockets so the listen port can bind again.
|
|
137
|
+
* Bare IPv6 rows (`::1`, `::`, etc.) are skipped (not coerced into IPv4 wildcards);
|
|
138
|
+
* IPv6 TCB reclamation is unsupported on this path.
|
|
139
|
+
*/
|
|
140
|
+
export function dropWindowsTcpRowsForLocalPort(port: number): WindowsTcpDropResult {
|
|
141
|
+
if (process.platform !== "win32" || !Number.isFinite(port) || port <= 0) {
|
|
142
|
+
return { dropped: 0, skippedIpv6: 0, accessDenied: 0 };
|
|
143
|
+
}
|
|
144
|
+
const setTcpEntry = loadSetTcpEntry();
|
|
145
|
+
if (!setTcpEntry) return { dropped: 0, skippedIpv6: 0, accessDenied: 0 };
|
|
146
|
+
|
|
147
|
+
let output = "";
|
|
148
|
+
try {
|
|
149
|
+
output = readNetstatAno();
|
|
150
|
+
} catch {
|
|
151
|
+
return { dropped: 0, skippedIpv6: 0, accessDenied: 0 };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const rows = parseTcpQuadsForLocalPort(output, Math.trunc(port));
|
|
155
|
+
let dropped = 0;
|
|
156
|
+
let skippedIpv6 = 0;
|
|
157
|
+
let accessDenied = 0;
|
|
158
|
+
for (const row of rows) {
|
|
159
|
+
const localDw = ipv4ToWinUint32(row.localAddr);
|
|
160
|
+
const remoteDw = ipv4ToWinUint32(row.remoteAddr);
|
|
161
|
+
if (localDw === null || remoteDw === null) {
|
|
162
|
+
if (isBareIpv6Address(row.localAddr) || isBareIpv6Address(row.remoteAddr)) {
|
|
163
|
+
skippedIpv6 += 1;
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const buf = new ArrayBuffer(20);
|
|
168
|
+
const view = new DataView(buf);
|
|
169
|
+
view.setUint32(0, 12, true); // MIB_TCP_STATE_DELETE_TCB
|
|
170
|
+
view.setUint32(4, localDw, true);
|
|
171
|
+
view.setUint32(8, htons(row.localPort), true);
|
|
172
|
+
view.setUint32(12, remoteDw, true);
|
|
173
|
+
view.setUint32(16, htons(row.remotePort), true);
|
|
174
|
+
try {
|
|
175
|
+
const rc = setTcpEntry(ptr(buf));
|
|
176
|
+
if (rc === 0) dropped += 1;
|
|
177
|
+
// 5 = ERROR_ACCESS_DENIED, 317 = non-elevated SetTcpEntry (MSDN).
|
|
178
|
+
else if (rc === 5 || rc === 317) accessDenied += 1;
|
|
179
|
+
} catch {
|
|
180
|
+
/* keep going */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { dropped, skippedIpv6, accessDenied };
|
|
184
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { durableBunPath } from "../lib/bun-runtime";
|
|
4
|
+
import type { WindowsTrayStatus } from "../tray/windows";
|
|
5
|
+
|
|
6
|
+
export type WindowsTrayAction = "install" | "start" | "stop" | "status" | "uninstall";
|
|
7
|
+
|
|
8
|
+
/** Run blocking registry/PowerShell waits outside the proxy event loop. */
|
|
9
|
+
export function runWindowsTrayAction(action: WindowsTrayAction): Promise<WindowsTrayStatus> {
|
|
10
|
+
if (action === "status") {
|
|
11
|
+
return import("../tray/windows").then(({ getWindowsTrayStatusAsync }) => getWindowsTrayStatusAsync());
|
|
12
|
+
}
|
|
13
|
+
const bun = durableBunPath();
|
|
14
|
+
const cli = join(import.meta.dir, "..", "cli", "index.ts");
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
execFile(bun, [cli, "tray", action, "--json"], {
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
env: process.env,
|
|
19
|
+
timeout: 30_000,
|
|
20
|
+
windowsHide: true,
|
|
21
|
+
maxBuffer: 256 * 1024,
|
|
22
|
+
}, (error, stdout, stderr) => {
|
|
23
|
+
const lines = stdout.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
24
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(lines[index]) as WindowsTrayStatus | { error?: unknown };
|
|
27
|
+
if ("supported" in parsed && typeof parsed.supported === "boolean") {
|
|
28
|
+
resolve(parsed);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const failure = parsed as { error?: unknown };
|
|
32
|
+
if (typeof failure.error === "string") {
|
|
33
|
+
reject(new Error(failure.error));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
} catch { /* scan earlier lines */ }
|
|
37
|
+
}
|
|
38
|
+
reject(new Error(error?.message || stderr.trim() || "Windows tray action returned no status"));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|