@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
package/src/pi/home.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi agent home paths.
|
|
3
|
+
*
|
|
4
|
+
* Default root is `~/.pi/agent` (Pi's documented global agent dir). `PI_HOME` may
|
|
5
|
+
* override the parent `~/.pi` (so `PI_HOME=/tmp/pi` → `/tmp/pi/agent/...`), matching
|
|
6
|
+
* the "home of the product" pattern used by Grok's `GROK_HOME`.
|
|
7
|
+
*
|
|
8
|
+
* We never invent a second state root under `~/.opencodex` for Pi files: Pi owns
|
|
9
|
+
* these paths; opencodex only reads/writes well-known keys inside them.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, statSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
export function resolvePiRoot(piHome?: string): string {
|
|
16
|
+
return piHome ?? process.env.PI_HOME ?? join(homedir(), ".pi");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolvePiAgentDir(piHome?: string): string {
|
|
20
|
+
return join(resolvePiRoot(piHome), "agent");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function piModelsPath(piHome?: string): string {
|
|
24
|
+
return join(resolvePiAgentDir(piHome), "models.json");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function piSettingsPath(piHome?: string): string {
|
|
28
|
+
return join(resolvePiAgentDir(piHome), "settings.json");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function piExtensionsDir(piHome?: string): string {
|
|
32
|
+
return join(resolvePiAgentDir(piHome), "extensions");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function piAgentDirExists(piHome?: string): boolean {
|
|
36
|
+
const dir = resolvePiAgentDir(piHome);
|
|
37
|
+
try {
|
|
38
|
+
return existsSync(dir) && statSync(dir).isDirectory();
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/pi/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export {
|
|
2
|
+
resolvePiRoot,
|
|
3
|
+
resolvePiAgentDir,
|
|
4
|
+
piModelsPath,
|
|
5
|
+
piSettingsPath,
|
|
6
|
+
piExtensionsDir,
|
|
7
|
+
piAgentDirExists,
|
|
8
|
+
} from "./home";
|
|
9
|
+
export {
|
|
10
|
+
buildPiOpencodexProvider,
|
|
11
|
+
injectPiModels,
|
|
12
|
+
removePiModels,
|
|
13
|
+
readPiModelsStatus,
|
|
14
|
+
type PiInjectModel,
|
|
15
|
+
type PiInjectResult,
|
|
16
|
+
type PiModelsStatus,
|
|
17
|
+
} from "./models";
|
|
18
|
+
export {
|
|
19
|
+
readPiSettings,
|
|
20
|
+
writePiSettings,
|
|
21
|
+
validatePiSettingsPatch,
|
|
22
|
+
projectPiSettings,
|
|
23
|
+
type PiCuratedSettings,
|
|
24
|
+
type PiSettingsStatus,
|
|
25
|
+
type PiSettingsWriteResult,
|
|
26
|
+
} from "./settings";
|
|
27
|
+
export {
|
|
28
|
+
validatePiPackageSource,
|
|
29
|
+
resolvePiBinary,
|
|
30
|
+
readPiPackages,
|
|
31
|
+
listPiPackagesDetailed,
|
|
32
|
+
installPiPackage,
|
|
33
|
+
removePiPackage,
|
|
34
|
+
type PiPackageEntry,
|
|
35
|
+
type PiPackagesStatus,
|
|
36
|
+
type PiPackageCommandResult,
|
|
37
|
+
} from "./packages";
|
|
38
|
+
export { readPiExtensions, type PiExtensionEntry, type PiExtensionsStatus } from "./extensions";
|
|
39
|
+
export { readPiStatus, piInstallHint, type PiStatus } from "./status";
|
|
40
|
+
export { syncPiConfig, type PiSyncDeps } from "./sync";
|
package/src/pi/models.ts
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inject / remove the managed `providers.opencodex` block in Pi's models.json.
|
|
3
|
+
*
|
|
4
|
+
* Ownership model (user choice: managed block only):
|
|
5
|
+
* - We own ONLY the provider key `opencodex`.
|
|
6
|
+
* - Other providers, top-level keys, and formatting of the rest of the file are
|
|
7
|
+
* preserved as far as JSON round-trip allows (re-pretty-printed with 2-space
|
|
8
|
+
* indent; unknown sibling keys stay).
|
|
9
|
+
* - Apply upserts `providers.opencodex`; remove deletes that key alone.
|
|
10
|
+
*
|
|
11
|
+
* Non-loopback binds refuse to write: the emitted apiKey is an env reference, and
|
|
12
|
+
* a remote baseUrl would silently send traffic somewhere the local dashboard
|
|
13
|
+
* cannot prove is this proxy (same policy as Grok inject).
|
|
14
|
+
*/
|
|
15
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
|
+
import { atomicWriteFile } from "../config";
|
|
18
|
+
import {
|
|
19
|
+
OPENCODE_PROVIDER_ID,
|
|
20
|
+
PI_API_KEY_ENV_REF,
|
|
21
|
+
buildClientConfig,
|
|
22
|
+
type ExportModel,
|
|
23
|
+
type PiGeneratedConfig,
|
|
24
|
+
type PiProviderBlock,
|
|
25
|
+
} from "../clients/config-export";
|
|
26
|
+
import { isLoopbackHostname, providerBaseHost } from "../codex/inject";
|
|
27
|
+
import { piAgentDirExists, piModelsPath, resolvePiAgentDir } from "./home";
|
|
28
|
+
|
|
29
|
+
export interface PiInjectModel {
|
|
30
|
+
namespaced: string;
|
|
31
|
+
provider: string;
|
|
32
|
+
id: string;
|
|
33
|
+
native?: boolean;
|
|
34
|
+
displayName?: string;
|
|
35
|
+
contextWindow?: number;
|
|
36
|
+
inputModalities?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PiInjectResult {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
changed: boolean;
|
|
42
|
+
message: string;
|
|
43
|
+
modelsPath: string;
|
|
44
|
+
modelCount: number;
|
|
45
|
+
skippedReason?: "no-pi-home" | "non-loopback" | "invalid-file";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PiModelsStatus {
|
|
49
|
+
modelsPath: string;
|
|
50
|
+
present: boolean;
|
|
51
|
+
baseUrl: string | null;
|
|
52
|
+
modelCount: number;
|
|
53
|
+
models: Array<{ id: string; name?: string; contextWindow?: number }>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
57
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readModelsDocument(path: string): { ok: true; doc: Record<string, unknown> } | { ok: false; error: string } {
|
|
61
|
+
if (!existsSync(path)) return { ok: true, doc: {} };
|
|
62
|
+
let raw: string;
|
|
63
|
+
try {
|
|
64
|
+
raw = readFileSync(path, "utf8");
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
67
|
+
}
|
|
68
|
+
if (raw.trim() === "") return { ok: true, doc: {} };
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
71
|
+
if (!isRecord(parsed)) return { ok: false, error: "models.json root must be an object" };
|
|
72
|
+
return { ok: true, doc: parsed };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function providersOf(doc: Record<string, unknown>): Record<string, unknown> {
|
|
79
|
+
const providers = doc.providers;
|
|
80
|
+
if (isRecord(providers)) return { ...providers };
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function asPiProviderBlock(value: unknown): PiProviderBlock | null {
|
|
85
|
+
if (!isRecord(value)) return null;
|
|
86
|
+
const baseUrl = typeof value.baseUrl === "string" ? value.baseUrl : null;
|
|
87
|
+
const api = typeof value.api === "string" ? value.api : null;
|
|
88
|
+
const apiKey = typeof value.apiKey === "string" ? value.apiKey : null;
|
|
89
|
+
const models = Array.isArray(value.models) ? value.models : null;
|
|
90
|
+
if (!baseUrl || !api || !apiKey || !models) return null;
|
|
91
|
+
return value as unknown as PiProviderBlock;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Build the opencodex provider block using the shared Pi export serializer. */
|
|
95
|
+
export function buildPiOpencodexProvider(
|
|
96
|
+
baseUrl: string,
|
|
97
|
+
models: readonly PiInjectModel[],
|
|
98
|
+
): PiProviderBlock {
|
|
99
|
+
const exportModels: ExportModel[] = models.map(model => ({
|
|
100
|
+
namespaced: model.namespaced,
|
|
101
|
+
provider: model.provider,
|
|
102
|
+
id: model.id,
|
|
103
|
+
...(model.native !== undefined ? { native: model.native } : {}),
|
|
104
|
+
...(model.displayName !== undefined ? { displayName: model.displayName } : {}),
|
|
105
|
+
...(model.contextWindow !== undefined ? { contextWindow: model.contextWindow } : {}),
|
|
106
|
+
...(model.inputModalities !== undefined ? { inputModalities: model.inputModalities } : {}),
|
|
107
|
+
}));
|
|
108
|
+
const generated = buildClientConfig("pi", { baseUrl, models: exportModels }) as PiGeneratedConfig;
|
|
109
|
+
const block = generated.providers[OPENCODE_PROVIDER_ID];
|
|
110
|
+
if (!block) {
|
|
111
|
+
// Defensive: the exporter always emits OPENCODE_PROVIDER_ID.
|
|
112
|
+
return {
|
|
113
|
+
baseUrl,
|
|
114
|
+
api: "openai-completions",
|
|
115
|
+
apiKey: PI_API_KEY_ENV_REF,
|
|
116
|
+
models: [],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return block;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function readPiModelsStatus(opts: { piHome?: string } = {}): PiModelsStatus {
|
|
123
|
+
const modelsPath = piModelsPath(opts.piHome);
|
|
124
|
+
const empty: PiModelsStatus = { modelsPath, present: false, baseUrl: null, modelCount: 0, models: [] };
|
|
125
|
+
const loaded = readModelsDocument(modelsPath);
|
|
126
|
+
if (!loaded.ok) return empty;
|
|
127
|
+
const block = asPiProviderBlock(providersOf(loaded.doc)[OPENCODE_PROVIDER_ID]);
|
|
128
|
+
if (!block) return empty;
|
|
129
|
+
const models = block.models.map(entry => ({
|
|
130
|
+
id: entry.id,
|
|
131
|
+
...(entry.name ? { name: entry.name } : {}),
|
|
132
|
+
...(typeof entry.contextWindow === "number" ? { contextWindow: entry.contextWindow } : {}),
|
|
133
|
+
}));
|
|
134
|
+
return {
|
|
135
|
+
modelsPath,
|
|
136
|
+
present: true,
|
|
137
|
+
baseUrl: block.baseUrl,
|
|
138
|
+
modelCount: models.length,
|
|
139
|
+
models,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Upsert providers.opencodex. Refuses non-loopback hostnames and missing agent dir
|
|
145
|
+
* (we create models.json, but not a whole ~/.pi tree the user never installed).
|
|
146
|
+
*/
|
|
147
|
+
export function injectPiModels(
|
|
148
|
+
port: number,
|
|
149
|
+
models: readonly PiInjectModel[],
|
|
150
|
+
opts: { hostname?: string; piHome?: string } = {},
|
|
151
|
+
): PiInjectResult {
|
|
152
|
+
const modelsPath = piModelsPath(opts.piHome);
|
|
153
|
+
const host = providerBaseHost(opts.hostname);
|
|
154
|
+
if (!isLoopbackHostname(host)) {
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
changed: false,
|
|
158
|
+
message: `Pi models inject skipped: hostname ${host} is not loopback.`,
|
|
159
|
+
modelsPath,
|
|
160
|
+
modelCount: 0,
|
|
161
|
+
skippedReason: "non-loopback",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (!piAgentDirExists(opts.piHome)) {
|
|
165
|
+
return {
|
|
166
|
+
ok: true,
|
|
167
|
+
changed: false,
|
|
168
|
+
message: `Pi models inject skipped: ${resolvePiAgentDir(opts.piHome)} does not exist (install Pi first).`,
|
|
169
|
+
modelsPath,
|
|
170
|
+
modelCount: 0,
|
|
171
|
+
skippedReason: "no-pi-home",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const baseUrl = `http://${host}:${port}/v1`;
|
|
176
|
+
const block = buildPiOpencodexProvider(baseUrl, models);
|
|
177
|
+
const loaded = readModelsDocument(modelsPath);
|
|
178
|
+
if (!loaded.ok) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
changed: false,
|
|
182
|
+
message: `Pi models inject failed: ${loaded.error}`,
|
|
183
|
+
modelsPath,
|
|
184
|
+
modelCount: 0,
|
|
185
|
+
skippedReason: "invalid-file",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const previous = JSON.stringify(loaded.doc);
|
|
190
|
+
const providers = providersOf(loaded.doc);
|
|
191
|
+
providers[OPENCODE_PROVIDER_ID] = block;
|
|
192
|
+
const nextDoc: Record<string, unknown> = { ...loaded.doc, providers };
|
|
193
|
+
const next = `${JSON.stringify(nextDoc, null, 2)}\n`;
|
|
194
|
+
if (next === `${previous.trim() === "" ? "{}" : JSON.stringify(loaded.doc, null, 2)}\n`) {
|
|
195
|
+
// Compare structured equality instead of string form of the read file (may lack trailing newline).
|
|
196
|
+
const same = JSON.stringify(loaded.doc) === JSON.stringify(nextDoc);
|
|
197
|
+
if (same) {
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
changed: false,
|
|
201
|
+
message: `Pi models unchanged (${block.models.length} model${block.models.length === 1 ? "" : "s"}).`,
|
|
202
|
+
modelsPath,
|
|
203
|
+
modelCount: block.models.length,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
mkdirSync(dirname(modelsPath), { recursive: true });
|
|
209
|
+
if (existsSync(modelsPath)) {
|
|
210
|
+
try {
|
|
211
|
+
copyFileSync(modelsPath, `${modelsPath}.opencodex.bak`);
|
|
212
|
+
} catch {
|
|
213
|
+
// Backup is best-effort; write still proceeds.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
atomicWriteFile(modelsPath, next);
|
|
217
|
+
return {
|
|
218
|
+
ok: true,
|
|
219
|
+
changed: true,
|
|
220
|
+
message: `Pi models updated: ${block.models.length} model${block.models.length === 1 ? "" : "s"} → ${modelsPath}`,
|
|
221
|
+
modelsPath,
|
|
222
|
+
modelCount: block.models.length,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Delete only providers.opencodex; leave the rest of models.json alone. */
|
|
227
|
+
export function removePiModels(opts: { piHome?: string } = {}): PiInjectResult {
|
|
228
|
+
const modelsPath = piModelsPath(opts.piHome);
|
|
229
|
+
if (!existsSync(modelsPath)) {
|
|
230
|
+
return {
|
|
231
|
+
ok: true,
|
|
232
|
+
changed: false,
|
|
233
|
+
message: "Pi models: nothing to remove (file absent).",
|
|
234
|
+
modelsPath,
|
|
235
|
+
modelCount: 0,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const loaded = readModelsDocument(modelsPath);
|
|
239
|
+
if (!loaded.ok) {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
changed: false,
|
|
243
|
+
message: `Pi models remove failed: ${loaded.error}`,
|
|
244
|
+
modelsPath,
|
|
245
|
+
modelCount: 0,
|
|
246
|
+
skippedReason: "invalid-file",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const providers = providersOf(loaded.doc);
|
|
250
|
+
if (!(OPENCODE_PROVIDER_ID in providers)) {
|
|
251
|
+
return {
|
|
252
|
+
ok: true,
|
|
253
|
+
changed: false,
|
|
254
|
+
message: "Pi models: opencodex provider not present.",
|
|
255
|
+
modelsPath,
|
|
256
|
+
modelCount: 0,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
delete providers[OPENCODE_PROVIDER_ID];
|
|
260
|
+
// Pi requires a top-level `providers` object even when empty — bare `{}` fails schema
|
|
261
|
+
// validation ("must have required properties providers"). Always keep the key.
|
|
262
|
+
const nextDoc: Record<string, unknown> = { ...loaded.doc, providers };
|
|
263
|
+
if (existsSync(modelsPath)) {
|
|
264
|
+
try {
|
|
265
|
+
copyFileSync(modelsPath, `${modelsPath}.opencodex.bak`);
|
|
266
|
+
} catch {
|
|
267
|
+
// best-effort
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
atomicWriteFile(modelsPath, `${JSON.stringify(nextDoc, null, 2)}\n`);
|
|
271
|
+
return {
|
|
272
|
+
ok: true,
|
|
273
|
+
changed: true,
|
|
274
|
+
message: `Pi models: removed providers.opencodex from ${modelsPath}`,
|
|
275
|
+
modelsPath,
|
|
276
|
+
modelCount: 0,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi package management via the real `pi` CLI.
|
|
3
|
+
*
|
|
4
|
+
* We deliberately do not re-implement npm/git install: packages run with full
|
|
5
|
+
* system access (Pi docs). Spawning `pi install|remove|list` keeps the same
|
|
6
|
+
* trust model the user already accepts when using Pi interactively, and avoids
|
|
7
|
+
* inventing a second installer that could diverge from Pi's pin/reconcile rules.
|
|
8
|
+
*
|
|
9
|
+
* Source strings are validated before spawn (no shell metacharacters; argv only).
|
|
10
|
+
*/
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { delimiter } from "node:path";
|
|
14
|
+
import { readPiSettings } from "./settings";
|
|
15
|
+
|
|
16
|
+
export interface PiPackageEntry {
|
|
17
|
+
/** Canonical source string as stored in settings (string form or object.source). */
|
|
18
|
+
source: string;
|
|
19
|
+
/** When settings used object form, optional resource filters. */
|
|
20
|
+
filters?: {
|
|
21
|
+
skills?: string[];
|
|
22
|
+
extensions?: string[];
|
|
23
|
+
prompts?: string[];
|
|
24
|
+
themes?: string[];
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PiPackagesStatus {
|
|
29
|
+
packages: PiPackageEntry[];
|
|
30
|
+
/** Output of `pi list` when available (may be empty if pi is missing). */
|
|
31
|
+
listOutput: string | null;
|
|
32
|
+
piBinary: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PiPackageCommandResult {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
message: string;
|
|
38
|
+
stdout: string;
|
|
39
|
+
stderr: string;
|
|
40
|
+
exitCode: number | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const SOURCE_MAX = 512;
|
|
44
|
+
const RUN_TIMEOUT_MS = 120_000;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Accept Pi's documented source forms only. Reject anything that looks like shell
|
|
48
|
+
* composition — we always pass argv, but defense in depth still matters.
|
|
49
|
+
*/
|
|
50
|
+
export function validatePiPackageSource(source: string): string | null {
|
|
51
|
+
const trimmed = source.trim();
|
|
52
|
+
if (!trimmed || trimmed.length > SOURCE_MAX) return "package source is empty or too long";
|
|
53
|
+
if (/[\0\n\r;|&`$]/.test(trimmed)) return "package source contains forbidden characters";
|
|
54
|
+
if (
|
|
55
|
+
trimmed.startsWith("npm:")
|
|
56
|
+
|| trimmed.startsWith("git:")
|
|
57
|
+
|| trimmed.startsWith("https://")
|
|
58
|
+
|| trimmed.startsWith("http://")
|
|
59
|
+
|| trimmed.startsWith("ssh://")
|
|
60
|
+
|| trimmed.startsWith("git://")
|
|
61
|
+
|| trimmed.startsWith("./")
|
|
62
|
+
|| trimmed.startsWith("../")
|
|
63
|
+
|| trimmed.startsWith("/")
|
|
64
|
+
|| /^[A-Za-z]:[\\/]/.test(trimmed) // Windows path
|
|
65
|
+
) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
// Bare npm package name / @scope/name (Pi accepts these inside packages array).
|
|
69
|
+
if (/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[^\s]+)?$/i.test(trimmed)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return "unsupported package source form";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function packageEntriesFromSettings(packages: unknown[] | undefined): PiPackageEntry[] {
|
|
76
|
+
if (!packages) return [];
|
|
77
|
+
const out: PiPackageEntry[] = [];
|
|
78
|
+
for (const entry of packages) {
|
|
79
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
80
|
+
out.push({ source: entry.trim() });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
84
|
+
const rec = entry as Record<string, unknown>;
|
|
85
|
+
const source = typeof rec.source === "string" ? rec.source.trim() : "";
|
|
86
|
+
if (!source) continue;
|
|
87
|
+
const filters: PiPackageEntry["filters"] = {};
|
|
88
|
+
for (const key of ["skills", "extensions", "prompts", "themes"] as const) {
|
|
89
|
+
const value = rec[key];
|
|
90
|
+
if (Array.isArray(value) && value.every(v => typeof v === "string")) {
|
|
91
|
+
filters[key] = value as string[];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
out.push({
|
|
95
|
+
source,
|
|
96
|
+
...(Object.keys(filters).length > 0 ? { filters } : {}),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Resolve `pi` on PATH (and common nvm locations are already on PATH when the user runs ocx). */
|
|
104
|
+
export function resolvePiBinary(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
105
|
+
const pathEnv = env.PATH ?? env.Path ?? "";
|
|
106
|
+
const names = process.platform === "win32" ? ["pi.cmd", "pi.exe", "pi"] : ["pi"];
|
|
107
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
108
|
+
if (!dir) continue;
|
|
109
|
+
for (const name of names) {
|
|
110
|
+
const candidate = `${dir.replace(/[/\\]$/, "")}${process.platform === "win32" ? "\\" : "/"}${name}`;
|
|
111
|
+
if (existsSync(candidate)) return candidate;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function readPiPackages(opts: { piHome?: string } = {}): PiPackagesStatus {
|
|
118
|
+
const settings = readPiSettings(opts);
|
|
119
|
+
return {
|
|
120
|
+
packages: packageEntriesFromSettings(settings.settings.packages),
|
|
121
|
+
listOutput: null,
|
|
122
|
+
piBinary: resolvePiBinary(),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function listPiPackagesDetailed(opts: { piHome?: string } = {}): Promise<PiPackagesStatus> {
|
|
127
|
+
const base = readPiPackages(opts);
|
|
128
|
+
if (!base.piBinary) return base;
|
|
129
|
+
const result = await runPiCli(base.piBinary, ["list"]);
|
|
130
|
+
return {
|
|
131
|
+
...base,
|
|
132
|
+
listOutput: result.ok || result.stdout ? result.stdout.trim() || result.stderr.trim() : null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function runPiCli(
|
|
137
|
+
binary: string,
|
|
138
|
+
args: string[],
|
|
139
|
+
opts: { timeoutMs?: number } = {},
|
|
140
|
+
): Promise<PiPackageCommandResult> {
|
|
141
|
+
const timeoutMs = opts.timeoutMs ?? RUN_TIMEOUT_MS;
|
|
142
|
+
return new Promise(resolve => {
|
|
143
|
+
const child = spawn(binary, args, {
|
|
144
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
145
|
+
env: process.env,
|
|
146
|
+
windowsHide: true,
|
|
147
|
+
});
|
|
148
|
+
let stdout = "";
|
|
149
|
+
let stderr = "";
|
|
150
|
+
let settled = false;
|
|
151
|
+
const timer = setTimeout(() => {
|
|
152
|
+
if (settled) return;
|
|
153
|
+
settled = true;
|
|
154
|
+
child.kill("SIGTERM");
|
|
155
|
+
resolve({
|
|
156
|
+
ok: false,
|
|
157
|
+
message: `pi ${args[0]} timed out after ${timeoutMs}ms`,
|
|
158
|
+
stdout,
|
|
159
|
+
stderr,
|
|
160
|
+
exitCode: null,
|
|
161
|
+
});
|
|
162
|
+
}, timeoutMs);
|
|
163
|
+
child.stdout?.on("data", chunk => { stdout += String(chunk); });
|
|
164
|
+
child.stderr?.on("data", chunk => { stderr += String(chunk); });
|
|
165
|
+
child.on("error", error => {
|
|
166
|
+
if (settled) return;
|
|
167
|
+
settled = true;
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
resolve({
|
|
170
|
+
ok: false,
|
|
171
|
+
message: error.message,
|
|
172
|
+
stdout,
|
|
173
|
+
stderr,
|
|
174
|
+
exitCode: null,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
child.on("close", code => {
|
|
178
|
+
if (settled) return;
|
|
179
|
+
settled = true;
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
const exitCode = code ?? null;
|
|
182
|
+
const ok = exitCode === 0;
|
|
183
|
+
resolve({
|
|
184
|
+
ok,
|
|
185
|
+
message: ok
|
|
186
|
+
? (stdout.trim() || `pi ${args.join(" ")} ok`)
|
|
187
|
+
: (stderr.trim() || stdout.trim() || `pi exited ${exitCode}`),
|
|
188
|
+
stdout,
|
|
189
|
+
stderr,
|
|
190
|
+
exitCode,
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function installPiPackage(source: string): Promise<PiPackageCommandResult> {
|
|
197
|
+
const error = validatePiPackageSource(source);
|
|
198
|
+
if (error) {
|
|
199
|
+
return { ok: false, message: error, stdout: "", stderr: "", exitCode: null };
|
|
200
|
+
}
|
|
201
|
+
const binary = resolvePiBinary();
|
|
202
|
+
if (!binary) {
|
|
203
|
+
return { ok: false, message: "pi binary not found on PATH", stdout: "", stderr: "", exitCode: null };
|
|
204
|
+
}
|
|
205
|
+
// Global install only (matches dashboard scope). Project-local needs a cwd we do not own.
|
|
206
|
+
return runPiCli(binary, ["install", source.trim()]);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function removePiPackage(source: string): Promise<PiPackageCommandResult> {
|
|
210
|
+
const error = validatePiPackageSource(source);
|
|
211
|
+
if (error) {
|
|
212
|
+
return { ok: false, message: error, stdout: "", stderr: "", exitCode: null };
|
|
213
|
+
}
|
|
214
|
+
const binary = resolvePiBinary();
|
|
215
|
+
if (!binary) {
|
|
216
|
+
return { ok: false, message: "pi binary not found on PATH", stdout: "", stderr: "", exitCode: null };
|
|
217
|
+
}
|
|
218
|
+
return runPiCli(binary, ["remove", source.trim()]);
|
|
219
|
+
}
|