@iislee/opencodex 2.11.0
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/LICENSE +21 -0
- package/README.md +225 -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 +451 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-DTpMHS4F.js +67 -0
- package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
- package/gui/dist/favicon.png +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/antigravity.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/claude.svg +1 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/copilot.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/cursor.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/gemini.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-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/kiro.svg +14 -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 +1 -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 +102 -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 +1003 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +72 -0
- package/src/adapters/client-fingerprint.ts +59 -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 +127 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +211 -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 +1214 -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 +55 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +329 -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 +576 -0
- package/src/adapters/cursor/protobuf-events.ts +563 -0
- package/src/adapters/cursor/protobuf-request.ts +714 -0
- package/src/adapters/cursor/request-builder.ts +255 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +505 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +52 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +303 -0
- package/src/adapters/google-antigravity-wire.ts +108 -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 +13 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +758 -0
- package/src/adapters/identity.ts +44 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +197 -0
- package/src/adapters/kiro-events.ts +179 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +96 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +215 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1898 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1005 -0
- package/src/adapters/openai-responses.ts +1137 -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/bridge.ts +1619 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +765 -0
- package/src/claude/agents-inject.ts +243 -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 +381 -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 +509 -0
- package/src/claude/model-info.ts +151 -0
- package/src/claude/outbound.ts +872 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +268 -0
- package/src/cli/account-auth.ts +223 -0
- package/src/cli/account-extended.ts +350 -0
- package/src/cli/account.ts +275 -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 +188 -0
- package/src/cli/claude.ts +286 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/combo.ts +119 -0
- package/src/cli/config-command.ts +145 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/doctor.ts +930 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +354 -0
- package/src/cli/index.ts +1113 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +142 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +20 -0
- package/src/cli/models-runtime.ts +212 -0
- package/src/cli/models.ts +336 -0
- package/src/cli/observe.ts +117 -0
- package/src/cli/opencode.ts +586 -0
- package/src/cli/pi.ts +188 -0
- package/src/cli/provider-runtime.ts +162 -0
- package/src/cli/provider.ts +463 -0
- package/src/cli/runtime-api.ts +325 -0
- package/src/cli/star-prompt.ts +155 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/sync-cloud.ts +283 -0
- package/src/cli/system-command.ts +112 -0
- package/src/cli/tray-proxy.ts +52 -0
- package/src/cli/v2.ts +173 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +377 -0
- package/src/clients/effective-status.ts +385 -0
- package/src/clients/probes/agy.ts +55 -0
- package/src/clients/probes/cc-switch.ts +110 -0
- package/src/clients/probes/claude.ts +90 -0
- package/src/clients/probes/codex.ts +125 -0
- package/src/clients/probes/grok.ts +29 -0
- package/src/clients/probes/opencode.ts +109 -0
- package/src/clients/probes/paseo.ts +55 -0
- package/src/clients/probes/pi.ts +55 -0
- package/src/cloud/onedrive-auth.ts +666 -0
- package/src/cloud/onedrive-graph.ts +108 -0
- package/src/cloud/settings.ts +75 -0
- package/src/cloud/sync.ts +212 -0
- package/src/cloud/types.ts +56 -0
- package/src/cloud/vault.ts +89 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +55 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +20 -0
- package/src/codex/app-server-processes.ts +756 -0
- package/src/codex/auth-api.ts +1540 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +352 -0
- package/src/codex/autostart-health.ts +149 -0
- package/src/codex/catalog/aggregation.ts +378 -0
- package/src/codex/catalog/bundled.ts +251 -0
- package/src/codex/catalog/effort.ts +355 -0
- package/src/codex/catalog/metadata.ts +180 -0
- package/src/codex/catalog/parsing.ts +456 -0
- package/src/codex/catalog/provider-fetch.ts +902 -0
- package/src/codex/catalog/sync.ts +620 -0
- package/src/codex/catalog.ts +12 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +969 -0
- package/src/codex/history-migration-guardian.ts +102 -0
- package/src/codex/history-provider.ts +776 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject.ts +799 -0
- package/src/codex/injected-marker.ts +72 -0
- package/src/codex/journal.ts +163 -0
- package/src/codex/main-account-cache.ts +32 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/model-cache.ts +227 -0
- package/src/codex/paths.ts +65 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +225 -0
- package/src/codex/project-config-warnings.ts +411 -0
- package/src/codex/quota.ts +411 -0
- package/src/codex/refresh.ts +53 -0
- package/src/codex/routing.ts +1477 -0
- package/src/codex/runtime.ts +538 -0
- package/src/codex/shim.ts +1189 -0
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +469 -0
- package/src/codex/sync.ts +130 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +41 -0
- package/src/combos/request.ts +62 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +326 -0
- package/src/config.ts +2356 -0
- package/src/generated/jawcode-model-metadata.ts +104 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +545 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +103 -0
- package/src/grok/usage-hook/report.mjs +348 -0
- package/src/grok/usage-hook.ts +278 -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 +829 -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/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 +202 -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 +71 -0
- package/src/lib/bun-stream-caps.ts +126 -0
- package/src/lib/config-ownership.ts +360 -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/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +165 -0
- package/src/lib/provider-outbound.ts +170 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +105 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +25 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +323 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/upstream-retry.ts +239 -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 +514 -0
- package/src/lib/winsw.ts +375 -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/cursor.ts +211 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +399 -0
- package/src/oauth/index.ts +1174 -0
- package/src/oauth/key-providers.ts +108 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +577 -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 +630 -0
- package/src/oauth/token-guardian.ts +303 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/pi/extensions.ts +72 -0
- package/src/pi/home.ts +42 -0
- package/src/pi/index.ts +40 -0
- package/src/pi/models.ts +278 -0
- package/src/pi/packages.ts +219 -0
- package/src/pi/settings.ts +365 -0
- package/src/pi/status.ts +68 -0
- package/src/pi/sync.ts +75 -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 +205 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/context-cap.ts +65 -0
- package/src/providers/derive.ts +339 -0
- package/src/providers/free-directory.ts +184 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/key-failover.ts +203 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/openai-sidecar.ts +175 -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 +82 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +150 -0
- package/src/providers/quota.ts +1260 -0
- package/src/providers/registry.ts +1600 -0
- package/src/providers/slug-codec.ts +67 -0
- package/src/providers/xai-transport.ts +141 -0
- package/src/reasoning-effort.ts +135 -0
- package/src/responses/compaction.ts +117 -0
- package/src/responses/parser.ts +656 -0
- package/src/responses/reasoning-envelope.ts +52 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +895 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +425 -0
- package/src/server/adapter-resolve.ts +80 -0
- package/src/server/auth-cors.ts +530 -0
- package/src/server/chat-completions.ts +368 -0
- package/src/server/claude-messages.ts +914 -0
- package/src/server/effort-policy.ts +172 -0
- package/src/server/gui-static.ts +123 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +476 -0
- package/src/server/index.ts +1126 -0
- package/src/server/lifecycle.ts +227 -0
- package/src/server/live.ts +598 -0
- package/src/server/management/agent-settings-routes.ts +1169 -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/clients-routes.ts +63 -0
- package/src/server/management/cloud-sync-routes.ts +266 -0
- package/src/server/management/combo-routes.ts +220 -0
- package/src/server/management/config-routes.ts +422 -0
- package/src/server/management/context.ts +31 -0
- package/src/server/management/logs-usage-routes.ts +707 -0
- package/src/server/management/model-routes.ts +525 -0
- package/src/server/management/oauth-account-routes.ts +563 -0
- package/src/server/management/provider-routes.ts +556 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +90 -0
- package/src/server/management/system-restart.ts +179 -0
- package/src/server/management/system-routes.ts +117 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +215 -0
- package/src/server/management-auth.ts +267 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +116 -0
- package/src/server/proxy-liveness.ts +201 -0
- package/src/server/relay-eager.ts +313 -0
- package/src/server/relay.ts +1049 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1046 -0
- package/src/server/responses/collaboration.ts +354 -0
- package/src/server/responses/compact.ts +384 -0
- package/src/server/responses/core.ts +2758 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +157 -0
- package/src/server/responses/passthrough-error.ts +78 -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 +224 -0
- package/src/server/responses.ts +9 -0
- package/src/server/search.ts +136 -0
- package/src/server/sse-payload-rewrite.ts +175 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +113 -0
- package/src/server/system-env.ts +413 -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 +471 -0
- package/src/service.ts +2554 -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 +290 -0
- package/src/tray/windows.ts +730 -0
- package/src/types.ts +1237 -0
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +407 -0
- package/src/update/job.ts +1520 -0
- package/src/update/notify.ts +257 -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 +164 -0
- package/src/usage/log.ts +658 -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 +125 -0
- package/src/vision/index.ts +467 -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 +664 -0
- package/src/web-search/parse.ts +220 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { loadConfig } from "../config";
|
|
2
|
+
import {
|
|
3
|
+
apiError,
|
|
4
|
+
apiJson,
|
|
5
|
+
classifyAccount,
|
|
6
|
+
fetchCodexRows,
|
|
7
|
+
fetchProviderQuotaReport,
|
|
8
|
+
fetchRows,
|
|
9
|
+
proxyUnreachable,
|
|
10
|
+
resolveBaseUrl,
|
|
11
|
+
type AccountDeps, type AccountStdin, type FamilyRows,
|
|
12
|
+
type ProviderQuotaDto, type ProviderQuotaReportDto,
|
|
13
|
+
} from "./account-api";
|
|
14
|
+
|
|
15
|
+
const MAIN_ID = "__main__";
|
|
16
|
+
const AUTO_NOTE = "auto (no pin — lowest-usage account is selected per request)";
|
|
17
|
+
const EXTENDED_USAGE = `Usage:
|
|
18
|
+
ocx account refresh <provider> [--json]
|
|
19
|
+
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
20
|
+
ocx account alias <provider> <id|main> <display-name|-> [--json]
|
|
21
|
+
ocx account remove <provider> <id|main> --yes [--json]
|
|
22
|
+
ocx account clear-cooldown <provider> <id|main> [--json]
|
|
23
|
+
ocx account add-key <provider> [--label <label>] [--json]`;
|
|
24
|
+
const PIPE_GUIDANCE = `Pipe the API key on stdin, for example:
|
|
25
|
+
ocx account add-key <provider> <<< "$MY_KEY"
|
|
26
|
+
security find-generic-password -w <item> | ocx account add-key <provider>`;
|
|
27
|
+
|
|
28
|
+
function flag(args: string[], value: string): boolean {
|
|
29
|
+
const index = args.indexOf(value);
|
|
30
|
+
if (index < 0) return false;
|
|
31
|
+
args.splice(index, 1);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function flagValue(args: string[], value: string): { found: boolean; value?: string } {
|
|
36
|
+
const index = args.indexOf(value);
|
|
37
|
+
if (index < 0) return { found: false };
|
|
38
|
+
if (index + 1 >= args.length) return { found: true };
|
|
39
|
+
const result = args[index + 1];
|
|
40
|
+
args.splice(index, 2);
|
|
41
|
+
return { found: true, value: result };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function usage(message?: string): number {
|
|
45
|
+
if (message) console.error(message);
|
|
46
|
+
console.error(EXTENDED_USAGE);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function configAndType(deps: AccountDeps, name: string) {
|
|
51
|
+
return classifyAccount(deps.loadConfigImpl?.() ?? loadConfig(), name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function familyFailure(result: FamilyRows, fallback: string): number | null {
|
|
55
|
+
if (result.networkDown) return proxyUnreachable();
|
|
56
|
+
if (result.errorJson) return apiError(result.errorJson, fallback);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function errorText(json: Record<string, unknown> | undefined, fallback: string): string {
|
|
61
|
+
return typeof json?.error === "string" ? json.error : fallback;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function resetIso(value: number | undefined): string | null {
|
|
65
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
66
|
+
const date = new Date(value < 10_000_000_000 ? value * 1000 : value);
|
|
67
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function refreshLine(row: FamilyRows["rows"][number]): string {
|
|
71
|
+
const parts = [row.id === MAIN_ID ? "main" : row.id, row.email, row.plan];
|
|
72
|
+
const quota = row.quota;
|
|
73
|
+
if (!quota || (quota.weeklyPercent === undefined && quota.monthlyPercent === undefined)) {
|
|
74
|
+
parts.push("quota: unknown");
|
|
75
|
+
} else {
|
|
76
|
+
if (quota.weeklyPercent !== undefined) parts.push(`weekly ${quota.weeklyPercent}%`);
|
|
77
|
+
const weeklyReset = resetIso(quota.weeklyResetAt);
|
|
78
|
+
if (weeklyReset) parts.push(`resets ${weeklyReset}`);
|
|
79
|
+
if (quota.monthlyPercent !== undefined) parts.push(`monthly ${quota.monthlyPercent}%`);
|
|
80
|
+
const monthlyReset = resetIso(quota.monthlyResetAt);
|
|
81
|
+
if (monthlyReset) parts.push(`resets ${monthlyReset}`);
|
|
82
|
+
}
|
|
83
|
+
if (row.needsReauth) parts.push("needs-reauth");
|
|
84
|
+
return parts.filter(Boolean).join(" ");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function quotaParts(quota: ProviderQuotaDto): string[] {
|
|
88
|
+
const parts: string[] = [];
|
|
89
|
+
const add = (label: string, percent: number | undefined, resetAt?: number) => {
|
|
90
|
+
if (percent === undefined) return;
|
|
91
|
+
parts.push(`${label} ${percent}%`);
|
|
92
|
+
const reset = resetIso(resetAt);
|
|
93
|
+
if (reset) parts.push(`resets ${reset}`);
|
|
94
|
+
};
|
|
95
|
+
add("5h", quota.fiveHourPercent, quota.fiveHourResetAt);
|
|
96
|
+
add("weekly", quota.weeklyPercent, quota.weeklyResetAt);
|
|
97
|
+
add("monthly", quota.monthlyPercent, quota.monthlyResetAt);
|
|
98
|
+
for (const window of quota.customWindows ?? []) add(window.label, window.percent, window.resetAt);
|
|
99
|
+
return parts;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function providerQuotaLine(name: string, report: ProviderQuotaReportDto): string {
|
|
103
|
+
return [name, ...quotaParts(report.quota)].join(" ");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function readStdinLine(deps: AccountDeps): Promise<string> {
|
|
107
|
+
const input: AccountStdin = deps.stdinImpl ?? process.stdin;
|
|
108
|
+
const timeoutMs = deps.stdinTimeoutMs ?? 15_000;
|
|
109
|
+
return await new Promise<string>((resolve, reject) => {
|
|
110
|
+
let buffer = "";
|
|
111
|
+
let settled = false;
|
|
112
|
+
const cleanup = () => {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
input.removeListener("data", onData);
|
|
115
|
+
input.removeListener("end", onEnd);
|
|
116
|
+
input.removeListener("error", onError);
|
|
117
|
+
};
|
|
118
|
+
const finish = (fn: () => void) => {
|
|
119
|
+
if (settled) return;
|
|
120
|
+
settled = true;
|
|
121
|
+
cleanup();
|
|
122
|
+
fn();
|
|
123
|
+
};
|
|
124
|
+
const onData = (chunk: unknown) => {
|
|
125
|
+
buffer += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
126
|
+
const newline = buffer.search(/[\r\n]/);
|
|
127
|
+
if (newline >= 0) finish(() => resolve(buffer.slice(0, newline).trim()));
|
|
128
|
+
};
|
|
129
|
+
const onEnd = () => finish(() => resolve(buffer.trim()));
|
|
130
|
+
const onError = (error: Error) => finish(() => reject(error));
|
|
131
|
+
const timer = setTimeout(() => finish(() => reject(new Error("timed out waiting for API key on stdin"))), timeoutMs);
|
|
132
|
+
input.on("data", onData);
|
|
133
|
+
input.on("end", onEnd);
|
|
134
|
+
input.on("error", onError);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function cmdRefresh(args: string[], deps: AccountDeps): Promise<number> {
|
|
139
|
+
const wantsJson = flag(args, "--json");
|
|
140
|
+
const name = args.shift();
|
|
141
|
+
if (!name || args.length) return usage();
|
|
142
|
+
const classified = configAndType(deps, name);
|
|
143
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
144
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
145
|
+
if (!baseUrl) return proxyUnreachable();
|
|
146
|
+
if (classified.type !== "codex") {
|
|
147
|
+
const result = await fetchProviderQuotaReport(deps, baseUrl, name);
|
|
148
|
+
if (result.status === 0) return proxyUnreachable();
|
|
149
|
+
if (result.status !== 200) return apiError(result.errorJson ?? {}, `failed to refresh ${name}`);
|
|
150
|
+
if (wantsJson) console.log(JSON.stringify({ provider: name, report: result.report }, null, 2));
|
|
151
|
+
else console.log(result.report ? providerQuotaLine(name, result.report) : `no quota report available for ${name}`);
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
const result = await fetchCodexRows(deps, baseUrl, true);
|
|
155
|
+
const failed = familyFailure(result, `failed to refresh ${name}`);
|
|
156
|
+
if (failed !== null) return failed;
|
|
157
|
+
if (wantsJson) console.log(JSON.stringify({ accounts: result.rows }, null, 2));
|
|
158
|
+
else for (const row of result.rows) console.log(refreshLine(row));
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise<number> {
|
|
163
|
+
const wantsJson = flag(args, "--json");
|
|
164
|
+
const name = args.shift();
|
|
165
|
+
const action = args.shift();
|
|
166
|
+
if (!name || !action) return usage();
|
|
167
|
+
const classified = configAndType(deps, name);
|
|
168
|
+
if ("error" in classified || classified.type !== "codex") {
|
|
169
|
+
return usage("Error: auto-switch only applies to the openai Codex account pool");
|
|
170
|
+
}
|
|
171
|
+
let threshold: number | undefined;
|
|
172
|
+
if (action === "on" && args.length === 0) threshold = 80;
|
|
173
|
+
else if (action === "off" && args.length === 0) threshold = 0;
|
|
174
|
+
else if (action === "threshold" && args.length === 1 && /^\d+$/.test(args[0]!)) threshold = Number(args[0]);
|
|
175
|
+
else if (action !== "status" || args.length !== 0) return usage();
|
|
176
|
+
if (threshold !== undefined && (!Number.isInteger(threshold) || threshold < 0 || threshold > 100)) {
|
|
177
|
+
return usage("Error: threshold must be an integer 0-100");
|
|
178
|
+
}
|
|
179
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
180
|
+
if (!baseUrl) return proxyUnreachable();
|
|
181
|
+
if (action === "status") {
|
|
182
|
+
const response = await apiJson(deps, baseUrl, "GET", "/api/codex-auth/active");
|
|
183
|
+
if (response.status === 0) return proxyUnreachable();
|
|
184
|
+
if (response.status !== 200 || typeof response.json.autoSwitchThreshold !== "number") {
|
|
185
|
+
return apiError(response.json, "failed to read auto-switch status");
|
|
186
|
+
}
|
|
187
|
+
threshold = response.json.autoSwitchThreshold;
|
|
188
|
+
} else {
|
|
189
|
+
const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/auto-switch", { threshold });
|
|
190
|
+
if (response.status === 0) return proxyUnreachable();
|
|
191
|
+
if (response.status !== 200) return apiError(response.json, "failed to update auto-switch");
|
|
192
|
+
}
|
|
193
|
+
const enabled = threshold! > 0;
|
|
194
|
+
if (wantsJson) console.log(JSON.stringify({ provider: name, autoSwitchThreshold: threshold, enabled }, null, 2));
|
|
195
|
+
else console.log(enabled ? `auto-switch: on (threshold ${threshold}%)` : "auto-switch: off");
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function deletePath(type: "codex" | "oauth" | "api-key", name: string, id: string): string {
|
|
200
|
+
if (type === "codex") return `/api/codex-auth/accounts?id=${encodeURIComponent(id)}`;
|
|
201
|
+
if (type === "oauth") return `/api/oauth/accounts?provider=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`;
|
|
202
|
+
return `/api/providers/keys?name=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function cmdRemove(args: string[], deps: AccountDeps): Promise<number> {
|
|
206
|
+
const wantsJson = flag(args, "--json");
|
|
207
|
+
const fail = (message: string): number => {
|
|
208
|
+
if (wantsJson) console.error(JSON.stringify({ error: message }));
|
|
209
|
+
else console.error(`Error: ${message}`);
|
|
210
|
+
return 1;
|
|
211
|
+
};
|
|
212
|
+
const confirmed = flag(args, "--yes");
|
|
213
|
+
const name = args.shift();
|
|
214
|
+
const requestedId = args.shift();
|
|
215
|
+
if (!name || !requestedId || args.length) return wantsJson ? fail("provider and account id are required") : usage();
|
|
216
|
+
if (!confirmed) {
|
|
217
|
+
const message = `Confirmation required. Re-run: ocx account remove ${name} ${requestedId} --yes`;
|
|
218
|
+
return wantsJson ? fail(message) : usage(message);
|
|
219
|
+
}
|
|
220
|
+
const classified = configAndType(deps, name);
|
|
221
|
+
if ("error" in classified) return wantsJson ? fail(classified.error) : usage(`Error: ${classified.error}`);
|
|
222
|
+
const id = classified.type === "codex" && requestedId === "main" ? MAIN_ID : requestedId;
|
|
223
|
+
if (classified.type === "codex" && id === MAIN_ID) return wantsJson
|
|
224
|
+
? fail("the main Codex App login cannot be removed")
|
|
225
|
+
: usage("Error: the main Codex App login cannot be removed");
|
|
226
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
227
|
+
if (!baseUrl) return fail("Proxy not reachable. Start it with 'ocx start' or 'ocx ensure'.");
|
|
228
|
+
const before = await fetchRows(deps, baseUrl, name, classified.type);
|
|
229
|
+
if (before.networkDown) return fail("Proxy not reachable. Start it with 'ocx start' or 'ocx ensure'.");
|
|
230
|
+
if (before.errorJson) return fail(errorText(before.errorJson, `failed to verify ${name} before removal`));
|
|
231
|
+
if (!before.rows.some(row => row.id === id)) return wantsJson
|
|
232
|
+
? fail(`account or key "${requestedId}" was not found`)
|
|
233
|
+
: usage(`Error: account or key "${requestedId}" was not found`);
|
|
234
|
+
const response = await apiJson(deps, baseUrl, "DELETE", deletePath(classified.type, name, id));
|
|
235
|
+
if (response.status === 0) return fail("Proxy not reachable. Start it with 'ocx start' or 'ocx ensure'.");
|
|
236
|
+
if (response.status !== 200) return fail(errorText(response.json, `failed to remove ${requestedId}`));
|
|
237
|
+
const after = await fetchRows(deps, baseUrl, name, classified.type);
|
|
238
|
+
if (after.networkDown || after.errorJson) {
|
|
239
|
+
const detail = after.networkDown ? "proxy not reachable" : typeof after.errorJson?.error === "string" ? after.errorJson.error : "unknown error";
|
|
240
|
+
return fail(`post-delete verification failed; delete may have succeeded: ${detail}`);
|
|
241
|
+
}
|
|
242
|
+
const removedActive = before.activeId === id;
|
|
243
|
+
const result = { ok: true, provider: name, id, removedActive, promotedActiveId: after.activeId };
|
|
244
|
+
if (wantsJson) console.log(JSON.stringify(result, null, 2));
|
|
245
|
+
else if (classified.type === "codex" && removedActive && after.activeId === null) console.log(`openai: ${AUTO_NOTE}`);
|
|
246
|
+
else if (classified.type === "oauth") console.log(after.rows.length ? `${name}: active account is now ${after.activeId}` : `${name}: no accounts remaining`);
|
|
247
|
+
else if (classified.type === "api-key") console.log(after.rows.length ? `${name}: active key is now ${after.activeId}` : `${name}: no keys remaining`);
|
|
248
|
+
else console.log(`${name}: removed account ${requestedId}`);
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function cmdAddKey(args: string[], deps: AccountDeps): Promise<number> {
|
|
253
|
+
const wantsJson = flag(args, "--json");
|
|
254
|
+
const labelArg = flagValue(args, "--label");
|
|
255
|
+
const name = args.shift();
|
|
256
|
+
if (!name || args.length || (labelArg.found && labelArg.value === undefined)) return usage();
|
|
257
|
+
const classified = configAndType(deps, name);
|
|
258
|
+
if ("error" in classified || classified.type !== "api-key") return usage("Error: add-key only applies to API-key providers");
|
|
259
|
+
const input: AccountStdin = deps.stdinImpl ?? process.stdin;
|
|
260
|
+
if (input.isTTY) return usage(PIPE_GUIDANCE);
|
|
261
|
+
let key: string;
|
|
262
|
+
try {
|
|
263
|
+
key = await readStdinLine(deps);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
return usage(`Error: ${error instanceof Error ? error.message : String(error)}\n${PIPE_GUIDANCE}`);
|
|
266
|
+
}
|
|
267
|
+
if (!key) return usage(`Error: API key input was empty\n${PIPE_GUIDANCE}`);
|
|
268
|
+
const label = labelArg.value?.trim();
|
|
269
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
270
|
+
if (!baseUrl) return proxyUnreachable();
|
|
271
|
+
const response = await apiJson(deps, baseUrl, "POST", "/api/providers/keys", { name, key, ...(label ? { label } : {}) });
|
|
272
|
+
if (response.status === 0) return proxyUnreachable();
|
|
273
|
+
if (response.status !== 201) return apiError(response.json, `failed to add a key for ${name}`);
|
|
274
|
+
const id = typeof response.json.id === "string" ? response.json.id : null;
|
|
275
|
+
// Redact the key inside the label BEFORE serialization — a key containing
|
|
276
|
+
// JSON-escaped characters (" or \) would otherwise survive the whole-output
|
|
277
|
+
// pass in escaped form (audit finding, Carver WP3-C).
|
|
278
|
+
const safeLabel = label ? label.replaceAll(key, "[redacted]") : undefined;
|
|
279
|
+
const result = { ok: true, id, ...(safeLabel ? { label: safeLabel } : {}) };
|
|
280
|
+
const output = wantsJson ? JSON.stringify(result, null, 2)
|
|
281
|
+
: `${name}: added API key ${id ?? ""}${safeLabel ? ` (${safeLabel})` : ""}`.trim();
|
|
282
|
+
console.log(output.replaceAll(key, "[redacted]"));
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Lift a quota cooldown on a Codex account.
|
|
288
|
+
*
|
|
289
|
+
* This is the user-facing escape from the lockout described in
|
|
290
|
+
* `devlog/_plan/260726_cooldown_lockout_hardening`: injected routing makes the proxy the
|
|
291
|
+
* only model path for Codex Desktop, so a stuck cooldown reads as "the whole app is dead".
|
|
292
|
+
*
|
|
293
|
+
* Codex accounts only. API-key pools already reset their own 429 cooldowns through key
|
|
294
|
+
* management (`clearKeyCooldowns`), and OAuth providers have no equivalent state here.
|
|
295
|
+
*/
|
|
296
|
+
export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promise<number> {
|
|
297
|
+
const wantsJson = flag(args, "--json");
|
|
298
|
+
const name = args.shift();
|
|
299
|
+
const requestedId = args.shift();
|
|
300
|
+
if (!name || !requestedId || args.length) return usage();
|
|
301
|
+
const classified = configAndType(deps, name);
|
|
302
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
303
|
+
if (classified.type !== "codex") {
|
|
304
|
+
return usage(`Error: ${name} is not a Codex account pool; cooldown clearing applies to Codex accounts only`);
|
|
305
|
+
}
|
|
306
|
+
const id = requestedId === "main" ? MAIN_ID : requestedId;
|
|
307
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
308
|
+
if (!baseUrl) return proxyUnreachable();
|
|
309
|
+
const response = await apiJson(deps, baseUrl, "POST", "/api/codex-auth/accounts/clear-cooldown", { id });
|
|
310
|
+
if (response.status === 0) return proxyUnreachable();
|
|
311
|
+
if (response.status !== 200) return apiError(response.json, `failed to clear cooldown for ${requestedId}`);
|
|
312
|
+
const cleared = response.json?.cleared === true;
|
|
313
|
+
if (wantsJson) console.log(JSON.stringify({ ok: true, provider: name, id, cleared }, null, 2));
|
|
314
|
+
else if (cleared) console.log(`${name}: cooldown lifted for ${requestedId}`);
|
|
315
|
+
else console.log(`${name}: no active cooldown for ${requestedId}`);
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export async function cmdAlias(args: string[], deps: AccountDeps): Promise<number> {
|
|
320
|
+
const wantsJson = flag(args, "--json");
|
|
321
|
+
const name = args.shift();
|
|
322
|
+
const requestedId = args.shift();
|
|
323
|
+
const requestedAlias = args.shift();
|
|
324
|
+
if (!name || !requestedId || requestedAlias === undefined || args.length) return usage();
|
|
325
|
+
const classified = configAndType(deps, name);
|
|
326
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
327
|
+
const id = classified.type === "codex" && requestedId === "main" ? MAIN_ID : requestedId;
|
|
328
|
+
if (id === MAIN_ID) return usage("Error: the main Codex App login cannot be renamed");
|
|
329
|
+
const alias = requestedAlias === "-" ? "" : requestedAlias.trim();
|
|
330
|
+
if (alias.length > 80 || /[\x00-\x1f\x7f]/.test(alias)) return usage("Error: alias must be at most 80 printable characters");
|
|
331
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
332
|
+
if (!baseUrl) return proxyUnreachable();
|
|
333
|
+
const path = classified.type === "codex"
|
|
334
|
+
? "/api/codex-auth/accounts/alias"
|
|
335
|
+
: classified.type === "oauth"
|
|
336
|
+
? "/api/oauth/accounts/alias"
|
|
337
|
+
: "/api/providers/keys/alias";
|
|
338
|
+
const body = classified.type === "codex"
|
|
339
|
+
? { id, alias }
|
|
340
|
+
: classified.type === "oauth"
|
|
341
|
+
? { provider: name, accountId: id, alias }
|
|
342
|
+
: { name, id, alias };
|
|
343
|
+
const response = await apiJson(deps, baseUrl, "PUT", path, body);
|
|
344
|
+
if (response.status === 0) return proxyUnreachable();
|
|
345
|
+
if (response.status !== 200) return apiError(response.json, `failed to rename ${requestedId}`);
|
|
346
|
+
const result = { ok: true, provider: name, id, alias: alias || null };
|
|
347
|
+
if (wantsJson) console.log(JSON.stringify(result, null, 2));
|
|
348
|
+
else console.log(alias ? `${name}: ${requestedId} is now “${alias}”` : `${name}: cleared alias for ${requestedId}`);
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/** `ocx account` — list and switch provider credentials (issue #180). */
|
|
2
|
+
import { loadConfig } from "../config";
|
|
3
|
+
import { providerCodexAccountMode } from "../providers/registry";
|
|
4
|
+
import type { OcxConfig } from "../types";
|
|
5
|
+
import { cmdAddKey, cmdAlias, cmdAutoSwitch, cmdClearCooldown, cmdRefresh, cmdRemove } from "./account-extended";
|
|
6
|
+
import { apiError, apiJson, classifyAccount, fetchRows, proxyUnreachable, resolveBaseUrl, type AccountDeps, type AccountRow, type AccountType, type ApiResult }
|
|
7
|
+
from "./account-api";
|
|
8
|
+
|
|
9
|
+
export { classifyAccount } from "./account-api";
|
|
10
|
+
export type { AccountDeps, AccountRow, AccountType, ClassifyResult } from "./account-api";
|
|
11
|
+
type TargetProvenance = "live-oauth-list" | "config" | "codex";
|
|
12
|
+
|
|
13
|
+
const MAIN_ALIAS = "main";
|
|
14
|
+
const MAIN_CODEX_ID = "__main__";
|
|
15
|
+
/** Replacement-style single-slot OAuth (no stable identity; not HTTP-derivable). */
|
|
16
|
+
const REPLACEMENT_STYLE_OAUTH = new Set(["kiro"]);
|
|
17
|
+
|
|
18
|
+
const ACCOUNT_USAGE = `Usage:
|
|
19
|
+
ocx account list [provider] [--json] [--all]
|
|
20
|
+
ocx account current <provider> [--json]
|
|
21
|
+
ocx account use <provider> <account-or-key-id|main> [--json]
|
|
22
|
+
ocx account refresh <provider> [--json]
|
|
23
|
+
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
24
|
+
ocx account alias <provider> <account-or-key-id> <display-name|-> [--json]
|
|
25
|
+
ocx account remove <provider> <account-or-key-id|main> --yes [--json]
|
|
26
|
+
ocx account clear-cooldown <provider> <account-id|main> [--json]
|
|
27
|
+
ocx account add-key <provider> [--label <label>] [--json]
|
|
28
|
+
ocx account login <provider> [--id <account-id>] [--reauth] [--code -] [--no-wait] [--json]
|
|
29
|
+
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
30
|
+
ocx account cancel <provider> [--flow <flow-id>] [--json]
|
|
31
|
+
ocx account reset-credits <account-id|main> [--consume --yes] [--json]
|
|
32
|
+
|
|
33
|
+
List and switch provider accounts and API-key pools (masked output only).
|
|
34
|
+
'main' selects the Codex App login for the openai account pool.`;
|
|
35
|
+
|
|
36
|
+
function consumeFlag(args: string[], flag: string): boolean {
|
|
37
|
+
const idx = args.indexOf(flag);
|
|
38
|
+
if (idx === -1) return false;
|
|
39
|
+
args.splice(idx, 1);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Returns an error message for leftover args, or null when clean. */
|
|
44
|
+
function leftoverArgsError(args: string[]): string | null {
|
|
45
|
+
if (args.length === 0) return null;
|
|
46
|
+
const unknown = args.filter(a => a.startsWith("-"));
|
|
47
|
+
return unknown.length > 0
|
|
48
|
+
? `Unknown flag(s): ${unknown.join(", ")}`
|
|
49
|
+
: `Unexpected argument(s): ${args.join(", ")}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function candidateNames(config: OcxConfig): string {
|
|
53
|
+
const names = new Set<string>(["openai"]);
|
|
54
|
+
for (const n of Object.keys(config.providers ?? {})) names.add(n);
|
|
55
|
+
return [...names].join(", ");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function displayId(id: string): string {
|
|
59
|
+
return id === MAIN_CODEX_ID ? MAIN_ALIAS : id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function statusText(row: AccountRow): string {
|
|
63
|
+
const parts: string[] = [];
|
|
64
|
+
if (row.active) parts.push(row.type === "codex" ? "selected" : "active");
|
|
65
|
+
if (row.needsReauth) parts.push("needs-reauth");
|
|
66
|
+
return parts.join(" ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function formatAccountTable(rows: AccountRow[]): string {
|
|
70
|
+
const header = ["PROVIDER", "TYPE", "ID", "PLAN/LABEL", "STATUS"];
|
|
71
|
+
const data = rows.map(r => {
|
|
72
|
+
const keyLabel = r.masked && r.label !== r.masked ? `${r.masked} (${r.label})` : r.masked;
|
|
73
|
+
return [r.provider, r.type, displayId(r.id), r.type === "api-key" ? keyLabel ?? "-" : r.label ?? "-", statusText(r)];
|
|
74
|
+
});
|
|
75
|
+
const widths = header.map((h, i) => Math.max(h.length, ...data.map(d => d[i]!.length)));
|
|
76
|
+
const line = (cols: string[]) => cols.map((c, i) => c.padEnd(widths[i]!)).join(" ").trimEnd();
|
|
77
|
+
return [line(header), ...data.map(line)].join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function cmdList(rest: string[], deps: AccountDeps): Promise<number> {
|
|
81
|
+
const wantsJson = consumeFlag(rest, "--json");
|
|
82
|
+
const showAll = consumeFlag(rest, "--all");
|
|
83
|
+
const name = rest.shift();
|
|
84
|
+
const leftover = leftoverArgsError(rest);
|
|
85
|
+
if (leftover) {
|
|
86
|
+
console.error(leftover);
|
|
87
|
+
console.error(ACCOUNT_USAGE);
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
const config = deps.loadConfigImpl?.() ?? loadConfig();
|
|
91
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
92
|
+
if (!baseUrl) return proxyUnreachable();
|
|
93
|
+
|
|
94
|
+
const targets: { name: string; type: AccountType; provenance: TargetProvenance }[] = [];
|
|
95
|
+
if (name) {
|
|
96
|
+
const c = classifyAccount(config, name);
|
|
97
|
+
if ("error" in c) {
|
|
98
|
+
console.error(`Error: ${c.error}. Known candidates: ${candidateNames(config)}`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
targets.push({ name, type: c.type, provenance: "config" });
|
|
102
|
+
} else {
|
|
103
|
+
const seen = new Set<string>();
|
|
104
|
+
const push = (n: string, provenance: TargetProvenance) => {
|
|
105
|
+
if (seen.has(n)) return;
|
|
106
|
+
seen.add(n);
|
|
107
|
+
const c = classifyAccount(config, n);
|
|
108
|
+
if ("error" in c) return; // fan-out silently skips no-credential providers
|
|
109
|
+
targets.push({ name: n, type: c.type, provenance });
|
|
110
|
+
};
|
|
111
|
+
push("openai", "codex");
|
|
112
|
+
const providersRes = await apiJson(deps, baseUrl, "GET", "/api/oauth/providers");
|
|
113
|
+
if (providersRes.status === 0) return proxyUnreachable();
|
|
114
|
+
if (providersRes.status !== 200) return apiError(providersRes.json, "failed to list OAuth providers");
|
|
115
|
+
if (Array.isArray(providersRes.json.providers)) {
|
|
116
|
+
for (const p of providersRes.json.providers) {
|
|
117
|
+
if (typeof p === "string") push(p, "live-oauth-list");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const n of Object.keys(config.providers ?? {})) push(n, "config");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const rows: AccountRow[] = [];
|
|
124
|
+
const notes: string[] = [];
|
|
125
|
+
for (const t of targets) {
|
|
126
|
+
const r = await fetchRows(deps, baseUrl, t.name, t.type);
|
|
127
|
+
if (r.networkDown) return proxyUnreachable();
|
|
128
|
+
if (r.errorJson) {
|
|
129
|
+
if (name) return apiError(r.errorJson, `failed to list ${t.name}`);
|
|
130
|
+
const errorText = typeof r.errorJson.error === "string" ? r.errorJson.error : "";
|
|
131
|
+
const skipUnknownKey = t.type === "api-key"
|
|
132
|
+
&& r.status === 404
|
|
133
|
+
&& errorText.includes("unknown provider");
|
|
134
|
+
const skipConfigOAuth = t.type === "oauth"
|
|
135
|
+
&& t.provenance === "config"
|
|
136
|
+
&& r.status === 400
|
|
137
|
+
&& errorText.includes("unknown oauth provider");
|
|
138
|
+
if (skipUnknownKey || skipConfigOAuth) continue;
|
|
139
|
+
return apiError(r.errorJson, `failed to list ${t.name}`);
|
|
140
|
+
}
|
|
141
|
+
if (r.rows.length === 0) {
|
|
142
|
+
if (showAll) notes.push(`${t.name}: no stored accounts or keys`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
rows.push(...r.rows);
|
|
146
|
+
if (t.type === "codex") {
|
|
147
|
+
if (r.activeId === null) notes.push("openai: auto (no pin — lowest-usage account is selected per request)");
|
|
148
|
+
if (providerCodexAccountMode("openai", config.providers?.openai) === "direct") {
|
|
149
|
+
notes.push("openai is in direct mode — the selection takes effect when pool mode is enabled");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (t.type === "oauth" && REPLACEMENT_STYLE_OAUTH.has(t.name)) {
|
|
153
|
+
notes.push(`${t.name}: single login slot — re-login replaces the current account`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (wantsJson) {
|
|
158
|
+
console.log(JSON.stringify({ accounts: rows, notes }, null, 2));
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
if (rows.length > 0) console.log(formatAccountTable(rows));
|
|
162
|
+
for (const n of notes) console.log(n);
|
|
163
|
+
if (rows.length === 0 && notes.length === 0) console.log("No stored accounts or keys.");
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function cmdCurrent(rest: string[], deps: AccountDeps): Promise<number> {
|
|
168
|
+
const wantsJson = consumeFlag(rest, "--json");
|
|
169
|
+
const name = rest.shift();
|
|
170
|
+
const leftover = leftoverArgsError(rest);
|
|
171
|
+
if (!name || leftover) {
|
|
172
|
+
if (leftover) console.error(leftover);
|
|
173
|
+
console.error(ACCOUNT_USAGE);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
const config = deps.loadConfigImpl?.() ?? loadConfig();
|
|
177
|
+
const c = classifyAccount(config, name);
|
|
178
|
+
if ("error" in c) {
|
|
179
|
+
console.error(`Error: ${c.error}. Known candidates: ${candidateNames(config)}`);
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
183
|
+
if (!baseUrl) return proxyUnreachable();
|
|
184
|
+
const r = await fetchRows(deps, baseUrl, name, c.type);
|
|
185
|
+
if (r.networkDown) return proxyUnreachable();
|
|
186
|
+
if (r.errorJson) return apiError(r.errorJson, `failed to read ${name}`);
|
|
187
|
+
|
|
188
|
+
const activeRow = r.rows.find(row => row.active) ?? null;
|
|
189
|
+
if (wantsJson) {
|
|
190
|
+
console.log(JSON.stringify({
|
|
191
|
+
provider: name,
|
|
192
|
+
type: c.type,
|
|
193
|
+
activeId: r.activeId,
|
|
194
|
+
autoSwitchThreshold: r.autoSwitchThreshold,
|
|
195
|
+
account: activeRow,
|
|
196
|
+
}, null, 2));
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
if (activeRow) {
|
|
200
|
+
console.log(formatAccountTable([activeRow]));
|
|
201
|
+
} else if (c.type === "codex" && r.activeId === null) {
|
|
202
|
+
console.log("openai: auto (no pin — lowest-usage account is selected per request)");
|
|
203
|
+
} else {
|
|
204
|
+
console.log(`${name}: no active account or key`);
|
|
205
|
+
}
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function cmdUse(rest: string[], deps: AccountDeps): Promise<number> {
|
|
210
|
+
const wantsJson = consumeFlag(rest, "--json");
|
|
211
|
+
const name = rest.shift();
|
|
212
|
+
const id = rest.shift();
|
|
213
|
+
const leftover = leftoverArgsError(rest);
|
|
214
|
+
if (!name || !id || leftover) {
|
|
215
|
+
if (leftover) console.error(leftover);
|
|
216
|
+
console.error(ACCOUNT_USAGE);
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
const config = deps.loadConfigImpl?.() ?? loadConfig();
|
|
220
|
+
const c = classifyAccount(config, name);
|
|
221
|
+
if ("error" in c) {
|
|
222
|
+
console.error(`Error: ${c.error}. Known candidates: ${candidateNames(config)}`);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
226
|
+
if (!baseUrl) return proxyUnreachable();
|
|
227
|
+
|
|
228
|
+
let res: ApiResult;
|
|
229
|
+
let activeId: string;
|
|
230
|
+
if (c.type === "codex") {
|
|
231
|
+
activeId = id === MAIN_ALIAS ? MAIN_CODEX_ID : id;
|
|
232
|
+
res = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/active", { accountId: activeId });
|
|
233
|
+
} else if (c.type === "oauth") {
|
|
234
|
+
activeId = id;
|
|
235
|
+
res = await apiJson(deps, baseUrl, "PUT", "/api/oauth/accounts/active", { provider: name, accountId: id });
|
|
236
|
+
} else {
|
|
237
|
+
activeId = id;
|
|
238
|
+
res = await apiJson(deps, baseUrl, "PUT", "/api/providers/keys/active", { name, id });
|
|
239
|
+
}
|
|
240
|
+
if (res.status === 0) return proxyUnreachable();
|
|
241
|
+
if (res.status !== 200) return apiError(res.json, `failed to switch ${name}`);
|
|
242
|
+
|
|
243
|
+
if (wantsJson) console.log(JSON.stringify({ ok: true, provider: name, type: c.type, activeId }, null, 2));
|
|
244
|
+
else console.log(`${name}: active ${c.type === "api-key" ? "key" : "account"} is now ${displayId(activeId)}`);
|
|
245
|
+
if (c.type === "codex") {
|
|
246
|
+
console.error("Applies to the next request after clearing existing pool affinity; in-flight requests keep their captured account.");
|
|
247
|
+
console.error("Note: pool strategy, quota/cooldown/reauthentication state, and failure recovery may later select another eligible account.");
|
|
248
|
+
console.error("Conversation context is replayed after account changes, but the provider-side prompt cache may be cold.");
|
|
249
|
+
}
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promise<number> {
|
|
254
|
+
const [sub, ...rest] = args;
|
|
255
|
+
try {
|
|
256
|
+
if (sub === "list") return await cmdList(rest, deps);
|
|
257
|
+
if (sub === "current") return await cmdCurrent(rest, deps);
|
|
258
|
+
if (sub === "use") return await cmdUse(rest, deps);
|
|
259
|
+
if (sub === "refresh") return await cmdRefresh(rest, deps);
|
|
260
|
+
if (sub === "auto-switch") return await cmdAutoSwitch(rest, deps);
|
|
261
|
+
if (sub === "alias" || sub === "rename") return await cmdAlias(rest, deps);
|
|
262
|
+
if (sub === "remove") return await cmdRemove(rest, deps);
|
|
263
|
+
if (sub === "clear-cooldown") return await cmdClearCooldown(rest, deps);
|
|
264
|
+
if (sub === "add-key") return await cmdAddKey(rest, deps);
|
|
265
|
+
if (["login", "reauth", "code", "cancel", "reset-credits"].includes(sub ?? "")) {
|
|
266
|
+
const { handleAccountAuthCommand } = await import("./account-auth");
|
|
267
|
+
return await handleAccountAuthCommand(sub!, rest, deps) ?? 1;
|
|
268
|
+
}
|
|
269
|
+
console.error(ACCOUNT_USAGE);
|
|
270
|
+
return 1;
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error(`account: ${err instanceof Error ? err.message : String(err)}`);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
}
|