@bitkyc08/opencodex 2.60.0 → 2.61.0-preview.20260922
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +5 -1
- package/src/adapters/anthropic.ts +16 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +10 -2
- package/src/adapters/command-code.ts +2 -1
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +13 -3
- package/src/adapters/cursor.ts +11 -2
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin.ts +75 -23
- package/src/adapters/google-antigravity-wire.ts +5 -2
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google.ts +29 -5
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat.ts +18 -57
- package/src/adapters/openai-responses/passthrough.ts +2 -0
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +10 -0
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +43 -4
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/index.ts +200 -61
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-listener.ts +2 -5
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/provider-models.ts +24 -15
- package/src/codex/catalog/retained-sync.ts +2 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/history-provider.ts +12 -1
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +52 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +87 -44
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +5 -41
- package/src/codex/shim-templates.ts +29 -3
- package/src/companion/settings.ts +132 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/load-degrade.ts +34 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/schema/config-schema.ts +27 -1
- package/src/config/schema/leaf-validators.ts +47 -0
- package/src/config.ts +1 -1
- package/src/generated/compatibility-version.json +418 -174
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +124 -18
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +49 -11
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug.ts +40 -0
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +59 -14
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/index.ts +24 -1
- package/src/oauth/login-cli.ts +80 -29
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/key-failover.ts +31 -1
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +124 -37
- package/src/providers/quota/vendor-probes-key.ts +37 -22
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +9 -4
- package/src/providers/registry/entries-extended.ts +29 -4
- package/src/providers/registry/model-seeds.ts +47 -10
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/schema.ts +3 -0
- package/src/router.ts +17 -2
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +26 -0
- package/src/server/chat-completions.ts +55 -2
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +55 -17
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +56 -10
- package/src/server/index/spend-ledger-lifecycle.ts +34 -8
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index.ts +21 -28
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +6 -0
- package/src/server/management/shared.ts +18 -1
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +8 -9
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/relay.ts +19 -2
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +114 -0
- package/src/server/request-metrics.ts +92 -30
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +90 -0
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +14 -1
- package/src/server/responses/passthrough-dispatch.ts +179 -35
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/request-prepare.ts +42 -1
- package/src/server/responses/request-send-budget.ts +12 -0
- package/src/server/responses/request-transport.ts +24 -4
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +4 -1
- package/src/server/search.ts +25 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +1 -1
- package/src/types/config.ts +37 -0
- package/src/types/provider.ts +73 -0
- package/src/types/request.ts +28 -2
- package/src/types/tools.ts +19 -0
- package/src/types.ts +3 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/web-search/alpha-search.ts +21 -1
- package/gui/dist/assets/index-BTuCbqQd.css +0 -1
- package/gui/dist/assets/index-DoBVdPHP.js +0 -134
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordCommittedDesktopGateway } from "../claude/desktop-gateway-state";
|
|
1
2
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { loadConfig, mutatePersistedConfig, withConfigMutationLockSync } from "../config";
|
|
@@ -15,13 +16,25 @@ import {
|
|
|
15
16
|
type DesktopFamily,
|
|
16
17
|
type DesktopProfile,
|
|
17
18
|
} from "../claude/desktop-profile";
|
|
18
|
-
import { writeDesktop3pConfig, type Desktop3pConfigMode, parseDesktop3pModeArgs } from "../claude/desktop-3p";
|
|
19
|
+
import { inspectDesktop3pConfigLibrary, removeDesktop3pStandardPivot, writeDesktop3pConfig, type Desktop3pConfigMode, parseDesktop3pModeArgs } from "../claude/desktop-3p";
|
|
20
|
+
import {
|
|
21
|
+
applyDesktopFirstParty,
|
|
22
|
+
captureDesktopFirstPartyRollback,
|
|
23
|
+
isClaudeDesktopMode,
|
|
24
|
+
recordClaudeDesktopMode,
|
|
25
|
+
removeDesktopFirstParty,
|
|
26
|
+
resolveClaudeDesktopApplyMode,
|
|
27
|
+
type ClaudeDesktopMode,
|
|
28
|
+
} from "../claude/desktop-first-party";
|
|
19
29
|
import { claudeDesktopPolicyWarning, probeClaudeDesktopPolicy } from "../claude/desktop-policy";
|
|
20
30
|
import { filterCatalogVisibleModels, desktopVisibleNativeSlugs, nativeContextLimits } from "../codex/catalog";
|
|
21
31
|
import { buildClaudeDesktopState, fetchAllModels } from "../server/management-api";
|
|
22
32
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
23
33
|
import { CliUsageError, runtimeRequest, takeJsonFlag } from "./runtime-api";
|
|
24
34
|
import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
35
|
+
import type { OcxConfig } from "../types";
|
|
36
|
+
|
|
37
|
+
const APPLY_FLAGS = ["--first-party", "--gateway", "--static", "--hybrid", "--discovery-only"];
|
|
25
38
|
|
|
26
39
|
function isFamily(value: string | undefined): value is DesktopFamily {
|
|
27
40
|
return !!value && (DESKTOP_FAMILIES as readonly string[]).includes(value);
|
|
@@ -29,7 +42,10 @@ function isFamily(value: string | undefined): value is DesktopFamily {
|
|
|
29
42
|
|
|
30
43
|
function printDesktopHelp(): void {
|
|
31
44
|
console.log(`Usage:
|
|
32
|
-
ocx claude desktop [apply] [--static|--hybrid|--discovery-only]
|
|
45
|
+
ocx claude desktop [apply] [--first-party | --gateway [--static|--hybrid|--discovery-only]]
|
|
46
|
+
--first-party (default) keep Desktop on claude.ai; route only the Code tab's Claude Code
|
|
47
|
+
through the local intercept proxy via ~/.claude/settings.json env
|
|
48
|
+
--gateway install the third-party gateway profile for the whole app
|
|
33
49
|
ocx claude desktop show [--json]
|
|
34
50
|
ocx claude desktop status [--json]
|
|
35
51
|
ocx claude desktop move <provider/model> <opus|fable|sonnet|haiku> [--default]
|
|
@@ -56,6 +72,7 @@ function saveLocalDesktopProfile(
|
|
|
56
72
|
expectedProfile: DesktopProfile | undefined,
|
|
57
73
|
expectedConnection: ClientConnectionState,
|
|
58
74
|
deps: ApplyProfileDeps,
|
|
75
|
+
gatewayWrite?: { fingerprint?: string },
|
|
59
76
|
): void {
|
|
60
77
|
withClientLifecycleSync(() => {
|
|
61
78
|
const outcome = mutatePersistedConfig(current => {
|
|
@@ -73,6 +90,10 @@ function saveLocalDesktopProfile(
|
|
|
73
90
|
if (JSON.stringify(current.claudeCode?.desktopProfile) !== JSON.stringify(expectedProfile)) {
|
|
74
91
|
throw new Error("desktop_profile_changed");
|
|
75
92
|
}
|
|
93
|
+
if (gatewayWrite) {
|
|
94
|
+
recordCommittedDesktopGateway(current, profile, gatewayWrite.fingerprint, new Date().toISOString());
|
|
95
|
+
return { changed: true, value: undefined };
|
|
96
|
+
}
|
|
76
97
|
const changed = JSON.stringify(current.claudeCode?.desktopProfile) !== JSON.stringify(profile);
|
|
77
98
|
if (changed) current.claudeCode = { ...(current.claudeCode ?? {}), desktopProfile: structuredClone(profile) };
|
|
78
99
|
return { changed, value: undefined };
|
|
@@ -135,6 +156,151 @@ async function applyConnectedDesktopProfile(
|
|
|
135
156
|
}
|
|
136
157
|
}
|
|
137
158
|
|
|
159
|
+
/** Persist the operator's Desktop mode choice; a failed marker save never undoes the apply. */
|
|
160
|
+
function saveDesktopMode(mode: ClaudeDesktopMode, deps: ApplyProfileDeps): boolean {
|
|
161
|
+
try {
|
|
162
|
+
return withClientLifecycleSync(() => {
|
|
163
|
+
const outcome = mutatePersistedConfig(current => recordClaudeDesktopMode(current, mode));
|
|
164
|
+
return outcome.status !== "unavailable";
|
|
165
|
+
}, deps.lifecycleLockDeps);
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type DesktopApplyTarget =
|
|
172
|
+
| { kind: "first-party" }
|
|
173
|
+
| { kind: "gateway"; mode: Desktop3pConfigMode };
|
|
174
|
+
|
|
175
|
+
/** Parse `ocx claude desktop apply` flags into a target; legacy gateway shape flags imply --gateway. */
|
|
176
|
+
/**
|
|
177
|
+
* Default mode when no flag is given: first-party wherever the local intercept proxy can
|
|
178
|
+
* run; a connected client (proxy lives on the hub) or a disabled intercept falls back to
|
|
179
|
+
* the gateway profile rather than pointing Claude Code at a proxy that does not exist.
|
|
180
|
+
*/
|
|
181
|
+
export function defaultDesktopApplyMode(
|
|
182
|
+
config: Pick<OcxConfig, "claudeCode" | "runtimeRole">,
|
|
183
|
+
connection: ClientConnectionState = readClientConnectionState(),
|
|
184
|
+
): ClaudeDesktopMode {
|
|
185
|
+
const resolved = resolveClaudeDesktopApplyMode(config);
|
|
186
|
+
if (resolved === "gateway") return resolved;
|
|
187
|
+
return connection.kind === "connected" ? "gateway" : "first-party";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function parseDesktopApplyArgs(
|
|
191
|
+
flags: string[],
|
|
192
|
+
config: Pick<OcxConfig, "claudeCode" | "runtimeRole">,
|
|
193
|
+
): { target: DesktopApplyTarget } | { error: string } {
|
|
194
|
+
const shapeFlags = flags.filter(arg => ["--static", "--hybrid", "--discovery-only"].includes(arg));
|
|
195
|
+
const wantsFirstParty = flags.includes("--first-party");
|
|
196
|
+
const wantsGateway = flags.includes("--gateway") || shapeFlags.length > 0;
|
|
197
|
+
if (wantsFirstParty && wantsGateway) return { error: "--first-party cannot be combined with --gateway or gateway shape flags." };
|
|
198
|
+
const unknown = flags.filter(arg => !["--first-party", "--gateway", "--static", "--hybrid", "--discovery-only"].includes(arg));
|
|
199
|
+
if (unknown.length > 0) return { error: `알 수 없는 인자: ${unknown.join(" ")}` };
|
|
200
|
+
const kind: ClaudeDesktopMode = wantsFirstParty ? "first-party" : wantsGateway ? "gateway" : defaultDesktopApplyMode(config);
|
|
201
|
+
if (kind === "first-party") return { target: { kind } };
|
|
202
|
+
const parsedMode = parseDesktop3pModeArgs(shapeFlags);
|
|
203
|
+
if ("error" in parsedMode) return parsedMode;
|
|
204
|
+
return { target: { kind, mode: parsedMode.mode } };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Why a gateway apply happened when the help text calls first-party the default.
|
|
209
|
+
*
|
|
210
|
+
* `resolveClaudeDesktopMode` keeps an existing install where it is: an explicit
|
|
211
|
+
* `claudeCode.desktopMode` wins, and a stored gateway apply marker keeps gateway. Both rules are
|
|
212
|
+
* right — a working Desktop install must not flip underneath its user because a default moved.
|
|
213
|
+
* Together they mean an existing gateway user never arrives at first-party without discovering
|
|
214
|
+
* `--first-party` unaided, while `ocx claude desktop --help` tells them first-party is "(default)".
|
|
215
|
+
*
|
|
216
|
+
* The fix is not to change the resolution. It is to say, at the moment of the apply, that the
|
|
217
|
+
* other mode exists and what selects it. Returns null when the user asked for gateway explicitly,
|
|
218
|
+
* because they already know, and when first-party is simply unavailable here — a connected client
|
|
219
|
+
* or a disabled intercept cannot run it, so offering it would be advice that fails.
|
|
220
|
+
*/
|
|
221
|
+
export function gatewayModeExplanation(input: {
|
|
222
|
+
requestedExplicitly: boolean;
|
|
223
|
+
config: Pick<OcxConfig, "claudeCode" | "runtimeRole">;
|
|
224
|
+
connection?: ClientConnectionState;
|
|
225
|
+
}): string[] {
|
|
226
|
+
if (input.requestedExplicitly) return [];
|
|
227
|
+
const connection = input.connection ?? readClientConnectionState();
|
|
228
|
+
if (connection.kind === "connected") return [];
|
|
229
|
+
// Only a stored preference is worth explaining. Without one, gateway was chosen because
|
|
230
|
+
// first-party cannot run here, and naming an unavailable alternative is advice that fails.
|
|
231
|
+
const savedMode = input.config.claudeCode?.desktopMode;
|
|
232
|
+
const hasSavedGateway = isClaudeDesktopMode(savedMode) && savedMode === "gateway";
|
|
233
|
+
const hasApplyMarker = input.config.claudeCode?.desktopProfile?.appliedFingerprint !== undefined;
|
|
234
|
+
if (!hasSavedGateway && !hasApplyMarker) return [];
|
|
235
|
+
const reason = hasSavedGateway
|
|
236
|
+
? "this machine has claudeCode.desktopMode saved as gateway"
|
|
237
|
+
: "this machine carries a previous gateway apply";
|
|
238
|
+
return [
|
|
239
|
+
`Applied the gateway profile because ${reason}; an existing install is never switched for you.`,
|
|
240
|
+
"First-party keeps Desktop on your claude.ai account and routes only the Code tab through the local proxy:",
|
|
241
|
+
" ocx claude desktop apply --first-party",
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* First-party apply: settings.json env only. The intercept pair the env points at runs inside
|
|
247
|
+
* the hub process, so this is a local-hub operation — a connected client machine cannot reach
|
|
248
|
+
* a loopback proxy on the hub and is refused rather than left with a dead `HTTPS_PROXY`.
|
|
249
|
+
*/
|
|
250
|
+
async function applyFirstPartyDesktop(
|
|
251
|
+
deps: ApplyProfileDeps,
|
|
252
|
+
): Promise<{ ok: boolean; path: string; reason?: string; warning?: string }> {
|
|
253
|
+
try { assertNoClientDisconnectPending(); } catch { return { ok: false, path: "", reason: "client_disconnect_pending" }; }
|
|
254
|
+
const connection = readClientConnectionState();
|
|
255
|
+
if (connection.kind === "connected") return { ok: false, path: "", reason: "first_party_requires_local_hub" };
|
|
256
|
+
if (connection.kind !== "disconnected") return { ok: false, path: "", reason: "client_connection_invalid" };
|
|
257
|
+
const config = loadConfig();
|
|
258
|
+
const desired = setIntegrationEnabled("claude-desktop", true);
|
|
259
|
+
if (!desired.ok) return { ok: false, path: "", reason: desired.message };
|
|
260
|
+
// Establish the replacement before deleting the working gateway. A refused
|
|
261
|
+
// cleanup restores only our managed env keys, preserving unrelated settings.
|
|
262
|
+
const rollback = captureDesktopFirstPartyRollback(config);
|
|
263
|
+
const applied = applyDesktopFirstParty(config);
|
|
264
|
+
if (!applied.ok) return { ok: false, path: applied.path, reason: applied.reason };
|
|
265
|
+
const appliedFingerprint = config.claudeCode?.desktopProfile?.appliedFingerprint ?? null;
|
|
266
|
+
const library = inspectDesktop3pConfigLibrary({ appliedFingerprint });
|
|
267
|
+
if (library.kind === "gateway_ours" || library.kind === "gateway_drifted") {
|
|
268
|
+
const removed = removeDesktop3pStandardPivot({ appliedFingerprint, replaceWhileEnabled: true });
|
|
269
|
+
if (!removed.ok) {
|
|
270
|
+
const restored = removed.changed || !applied.changed || rollback();
|
|
271
|
+
const modeSaved = !removed.changed || saveDesktopMode("first-party", deps);
|
|
272
|
+
const warning = [restored ? "" : "first-party settings rollback did not complete",
|
|
273
|
+
modeSaved ? "" : "first-party is active but its mode marker was not saved"].filter(Boolean).join("; ");
|
|
274
|
+
return { ok: false, path: library.selectedProfilePath ?? "", reason: removed.kind === "cleanup_incomplete" ? "gateway_cleanup_incomplete" : `gateway_profile_active:${removed.reason ?? removed.kind}`,
|
|
275
|
+
...(warning ? { warning } : {}) };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const saved = saveDesktopMode("first-party", deps);
|
|
279
|
+
return {
|
|
280
|
+
ok: true,
|
|
281
|
+
path: applied.path,
|
|
282
|
+
...(saved ? {} : { warning: "desktop mode marker was not saved" }),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function applyDesktop(
|
|
287
|
+
profile: DesktopProfile | undefined,
|
|
288
|
+
target: DesktopApplyTarget,
|
|
289
|
+
deps: ApplyProfileDeps = {},
|
|
290
|
+
): Promise<{ ok: boolean; path: string; reason?: string; warning?: string }> {
|
|
291
|
+
if (target.kind === "first-party") return applyFirstPartyDesktop(deps);
|
|
292
|
+
const result = await applyProfile(profile, target.mode, deps);
|
|
293
|
+
if (!result.ok) return result;
|
|
294
|
+
const modeSaved = saveDesktopMode("gateway", deps);
|
|
295
|
+
const warning = [result.warning, modeSaved ? "" : "desktop mode marker was not saved"].filter(Boolean).join(" ");
|
|
296
|
+
// The gateway mode is committed before retiring first-party settings.
|
|
297
|
+
const removed = removeDesktopFirstParty();
|
|
298
|
+
if (!removed.ok) return { ok: false, path: removed.path, reason: "first_party_settings_unreadable",
|
|
299
|
+
warning: ["gateway applied; first-party cleanup remains incomplete", warning].filter(Boolean).join(" ") };
|
|
300
|
+
if (warning) return { ...result, warning };
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
|
|
138
304
|
export async function applyProfile(
|
|
139
305
|
profile: DesktopProfile | undefined,
|
|
140
306
|
mode: Desktop3pConfigMode,
|
|
@@ -205,8 +371,13 @@ export async function applyProfile(
|
|
|
205
371
|
nativeContextLimits(config),
|
|
206
372
|
deps.lifecycleLockDeps,
|
|
207
373
|
);
|
|
374
|
+
let stateWarning: string | undefined;
|
|
375
|
+
if (result.written) {
|
|
376
|
+
try { saveLocalDesktopProfile(state.profile, state.profile, connection, deps, { fingerprint: result.fingerprint }); }
|
|
377
|
+
catch { stateWarning = "gateway applied but its committed mode/profile state was not saved"; }
|
|
378
|
+
}
|
|
208
379
|
const policyState = (deps.probeClaudeDesktopPolicy ?? probeClaudeDesktopPolicy)();
|
|
209
|
-
const warning = result.written ? claudeDesktopPolicyWarning(policyState) : undefined;
|
|
380
|
+
const warning = [result.written ? claudeDesktopPolicyWarning(policyState) : undefined, stateWarning].filter(Boolean).join(" ");
|
|
210
381
|
return {
|
|
211
382
|
ok: result.written,
|
|
212
383
|
path: result.path,
|
|
@@ -222,24 +393,42 @@ export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProf
|
|
|
222
393
|
return 0;
|
|
223
394
|
}
|
|
224
395
|
|
|
225
|
-
//
|
|
226
|
-
const
|
|
227
|
-
const applyInvocation = argv.length === 0 || command === "apply" ||
|
|
396
|
+
// Mode flags remain apply aliases and are parsed before subcommands.
|
|
397
|
+
const applyFlags = argv.filter(arg => APPLY_FLAGS.includes(arg));
|
|
398
|
+
const applyInvocation = argv.length === 0 || command === "apply" || applyFlags.length > 0;
|
|
228
399
|
if (applyInvocation) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
const parsedMode = parseDesktop3pModeArgs(legacyFlags);
|
|
235
|
-
if ("error" in parsedMode) { console.error(parsedMode.error); return 2; }
|
|
400
|
+
const rest = argv.filter(arg => arg !== "apply");
|
|
401
|
+
const parsedTarget = parseDesktopApplyArgs(rest, loadConfig());
|
|
402
|
+
if ("error" in parsedTarget) { console.error(parsedTarget.error); return 2; }
|
|
403
|
+
const { target } = parsedTarget;
|
|
236
404
|
try {
|
|
237
|
-
const result = await
|
|
405
|
+
const result = await applyDesktop(undefined, target, deps);
|
|
238
406
|
if (!result.ok) {
|
|
239
407
|
console.error(`설정 적용 실패: ${result.reason ?? "unknown error"}`);
|
|
408
|
+
if (result.warning) console.warn(result.warning);
|
|
409
|
+
if (result.reason?.startsWith("gateway_")) {
|
|
410
|
+
console.error("The gateway profile could not be removed safely, so first-party mode was not applied. Turn the integration off (dashboard toggle) and retry, or keep gateway with `ocx claude desktop apply --gateway`.");
|
|
411
|
+
} else if (result.reason === "foreign_env") {
|
|
412
|
+
console.error(`~/.claude/settings.json already sets HTTPS_PROXY or NODE_EXTRA_CA_CERTS to a value opencodex does not own (${result.path}). Remove them or use --gateway.`);
|
|
413
|
+
} else if (result.reason === "intercept_disabled") {
|
|
414
|
+
console.error("First-party mode needs the Claude intercept proxy (claudeCode.intercept.enabled on a hub). Use --gateway instead.");
|
|
415
|
+
} else if (result.reason === "first_party_requires_local_hub") {
|
|
416
|
+
console.error("First-party mode runs on the hub machine only; on a connected client use --gateway.");
|
|
417
|
+
}
|
|
240
418
|
return 1;
|
|
241
419
|
}
|
|
242
|
-
|
|
420
|
+
if (target.kind === "first-party") {
|
|
421
|
+
console.log(`Claude Desktop first-party 설정을 적용했습니다: ${result.path}`);
|
|
422
|
+
console.log("Desktop 앱 설정은 그대로이며, Code 탭의 Claude Code만 로컬 프록시를 거칩니다.");
|
|
423
|
+
} else {
|
|
424
|
+
console.log(`Claude Desktop gateway 설정을 적용했습니다: ${result.path}`);
|
|
425
|
+
for (const line of gatewayModeExplanation({
|
|
426
|
+
requestedExplicitly: applyFlags.some(flag => flag !== "--first-party"),
|
|
427
|
+
config: loadConfig(),
|
|
428
|
+
})) {
|
|
429
|
+
console.log(line);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
243
432
|
// The write landed; only the bookkeeping marker did not. Saying nothing
|
|
244
433
|
// would leave the saved-vs-applied display wrong with no explanation.
|
|
245
434
|
if (result.warning) console.warn(`⚠️ ${result.warning}`);
|
|
@@ -336,7 +525,8 @@ export async function handleClaudeDesktopCommand(argv: string[], deps: ApplyProf
|
|
|
336
525
|
}
|
|
337
526
|
saveLocalDesktopProfile(reconciled, config.claudeCode?.desktopProfile, connection, deps);
|
|
338
527
|
if (flags.includes("--apply")) {
|
|
339
|
-
|
|
528
|
+
// A routing profile is a gateway-mode artifact: importing with --apply installs it there.
|
|
529
|
+
const result = await applyDesktop(reconciled, { kind: "gateway", mode: "static" }, deps);
|
|
340
530
|
if (!result.ok) { console.error(`프로필은 저장했지만 Desktop 적용에 실패했습니다: ${result.reason ?? "unknown error"}`); return 1; }
|
|
341
531
|
if (result.warning) console.warn(`⚠️ ${result.warning}`);
|
|
342
532
|
}
|
|
@@ -19,6 +19,9 @@ export function skipsCodexShimAutoRestore(command: string | undefined, args: str
|
|
|
19
19
|
if (command === "uninstall" || command === "remove") return true;
|
|
20
20
|
// `lab` is read-only inspection; it must not trigger shim side effects.
|
|
21
21
|
if (command === "lab") return true;
|
|
22
|
+
// `resolve` is read-only inspection for embedding shells: a lookup made to populate
|
|
23
|
+
// a consent surface must not trigger a shim repair side effect first.
|
|
24
|
+
if (command === "resolve") return true;
|
|
22
25
|
// The entire updater-inspection namespace is zero-effect, including malformed
|
|
23
26
|
// or future actions. A later `apply` implementation must own its preflight.
|
|
24
27
|
if (command === "system" && args[1] === "codex-cli-update") return true;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CliUsageError, printData, rejectArgs, runCliAction, runtimeRequest, takeFlag, type RuntimeApiDeps } from "./runtime-api";
|
|
2
|
+
|
|
3
|
+
const USAGE = `Usage:
|
|
4
|
+
ocx companion [show] [--json]
|
|
5
|
+
ocx companion set <key>=<value> [...] [--json]
|
|
6
|
+
ocx companion reset [--json]`;
|
|
7
|
+
|
|
8
|
+
function parseValue(raw: string): unknown {
|
|
9
|
+
if (raw === "null") return null;
|
|
10
|
+
try { return JSON.parse(raw); } catch { return raw; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function show(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
14
|
+
const args = [...argv];
|
|
15
|
+
const wantsJson = takeFlag(args, "--json");
|
|
16
|
+
rejectArgs(args, USAGE);
|
|
17
|
+
printData(await runtimeRequest("/api/companion/settings", {}, deps), wantsJson);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function set(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
21
|
+
const args = [...argv];
|
|
22
|
+
const wantsJson = takeFlag(args, "--json");
|
|
23
|
+
if (args.length === 0) throw new CliUsageError("companion set requires key=value assignments", USAGE);
|
|
24
|
+
const patch: Record<string, unknown> = {};
|
|
25
|
+
for (const assignment of args) {
|
|
26
|
+
const separator = assignment.indexOf("=");
|
|
27
|
+
if (separator <= 0) throw new CliUsageError(`invalid companion setting "${assignment}"; use key=value`, USAGE);
|
|
28
|
+
patch[assignment.slice(0, separator)] = parseValue(assignment.slice(separator + 1));
|
|
29
|
+
}
|
|
30
|
+
printData(await runtimeRequest("/api/companion/settings", {
|
|
31
|
+
method: "PUT",
|
|
32
|
+
body: JSON.stringify({ settings: patch }),
|
|
33
|
+
}, deps), wantsJson, ["Companion settings saved."]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function reset(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
37
|
+
const args = [...argv];
|
|
38
|
+
const wantsJson = takeFlag(args, "--json");
|
|
39
|
+
rejectArgs(args, USAGE);
|
|
40
|
+
printData(await runtimeRequest("/api/companion/settings", {
|
|
41
|
+
method: "PUT",
|
|
42
|
+
body: JSON.stringify({ reset: true }),
|
|
43
|
+
}, deps), wantsJson, ["Companion settings reset."]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function handleCompanionCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
47
|
+
return runCliAction(async () => {
|
|
48
|
+
const [sub = "show", ...rest] = argv;
|
|
49
|
+
if (sub === "show") await show(rest, deps);
|
|
50
|
+
else if (sub === "set") await set(rest, deps);
|
|
51
|
+
else if (sub === "reset") await reset(rest, deps);
|
|
52
|
+
else throw new CliUsageError(`unknown companion command ${sub}`, USAGE);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const COMPANION_USAGE = USAGE;
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -29,6 +29,8 @@ import { stripGrokConfig } from "../grok/inject";
|
|
|
29
29
|
import { handleRestartScopeAfterWrite, readRestartScope, type RestartScope } from "./restart-scope";
|
|
30
30
|
import { normalizeUpdateChannel, runGuiUpdateWorker } from "../update/job";
|
|
31
31
|
import { isJsonOption, takeFlag, terminalSafeError } from "./runtime-api";
|
|
32
|
+
import { printStopSummary, type StopOutcome } from "./stop-report";
|
|
33
|
+
import type { ResolveArgs } from "./resolve";
|
|
32
34
|
import type { ClientConnectionState } from "../client/state";
|
|
33
35
|
import { OCX_NATIVE_REPLAY_RECOVERY_NOTE } from "../responses/compaction";
|
|
34
36
|
|
|
@@ -44,8 +46,9 @@ export interface CliDispatchDeps {
|
|
|
44
46
|
/** Spawn a detached proxy child (stdio ignore, unref'd, provenance env). */
|
|
45
47
|
spawnDetached: (argv: readonly string[]) => void;
|
|
46
48
|
handleStart: () => Promise<void>;
|
|
47
|
-
handleStop: () => Promise<
|
|
49
|
+
handleStop: () => Promise<StopOutcome>;
|
|
48
50
|
handleEnsure: (options?: { existingIsSuccess?: boolean }) => Promise<boolean>;
|
|
51
|
+
handleResolve: (args: ResolveArgs) => Promise<number>;
|
|
49
52
|
handleTrayProxyStart: (existingIsSuccess?: boolean) => Promise<boolean>;
|
|
50
53
|
handleTrayProxyRestart: () => Promise<void>;
|
|
51
54
|
handleRestartStartWhenStopped: () => Promise<boolean | "skipped">;
|
|
@@ -98,11 +101,39 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
98
101
|
stop: async deps => {
|
|
99
102
|
// Downtime warning lives HERE, not in handleStop: `restart`/tray-restart callers
|
|
100
103
|
// re-start the proxy immediately, so warning there would contradict the next line.
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
const warning = "⚠️ Codex/Claude requests through the proxy will fail until it is restarted ('ocx start' or 'ocx service start').";
|
|
105
|
+
if (!takeFlag(deps.args.slice(1), "--json")) {
|
|
106
|
+
// handleStop returns the structured outcome now; an object is always truthy, so
|
|
107
|
+
// the warning must key on .ok — otherwise a failed stop would still claim downtime.
|
|
108
|
+
if ((await deps.handleStop()).ok) console.log(warning);
|
|
109
|
+
return Number(process.exitCode ?? 0);
|
|
103
110
|
}
|
|
111
|
+
// --json is a reporting layer over the SAME stop path: the receipt, the drain, the
|
|
112
|
+
// respawn verification and the client-config restore run unchanged. Human output
|
|
113
|
+
// still prints, but on stderr, so stdout carries exactly one JSON summary document.
|
|
114
|
+
// The exit code (0/1/79/80) crosses the process boundary untouched — the shell reads
|
|
115
|
+
// it from the child, and the stop-contract codes must survive the JSON mode.
|
|
116
|
+
const humanLog = console.log;
|
|
117
|
+
console.log = console.error;
|
|
118
|
+
let outcome: StopOutcome | undefined;
|
|
119
|
+
try {
|
|
120
|
+
outcome = await deps.handleStop();
|
|
121
|
+
if (outcome.ok) console.log(warning);
|
|
122
|
+
} finally {
|
|
123
|
+
console.log = humanLog;
|
|
124
|
+
}
|
|
125
|
+
// A throw above propagates after the finally restores the console, so reaching here
|
|
126
|
+
// with an undefined outcome cannot happen; the guard keeps the assignment provable.
|
|
127
|
+
if (outcome) printStopSummary(outcome.summary);
|
|
104
128
|
return Number(process.exitCode ?? 0);
|
|
105
129
|
},
|
|
130
|
+
resolve: async deps => {
|
|
131
|
+
// Same fail-closed shape as `ready`: parseCliHead pre-parsed the verb before any
|
|
132
|
+
// preflight side effect, so a missing resolveArgs means dispatch diverged. Refuse
|
|
133
|
+
// with code 64 and perform NO I/O.
|
|
134
|
+
if (!deps.head.resolveArgs) return 64;
|
|
135
|
+
return await deps.handleResolve(deps.head.resolveArgs);
|
|
136
|
+
},
|
|
106
137
|
restore: async deps => {
|
|
107
138
|
const restoreArgs = deps.args.slice(1);
|
|
108
139
|
const restoreJson = takeFlag(restoreArgs, "--json");
|
|
@@ -615,7 +646,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
615
646
|
const guiUrl = selectDefaultGuiUrl(config, live, deps.probeHostname);
|
|
616
647
|
console.log(`Opening ${guiUrl}`);
|
|
617
648
|
const { openUrl } = await import("../lib/open-url");
|
|
618
|
-
|
|
649
|
+
// Awaited so a launcher that never opened anything is said out loud (#5261). Still exit
|
|
650
|
+
// 0: the proxy is serving and the URL above is reachable, only the launch did not happen.
|
|
651
|
+
if ((await openUrl(guiUrl)).status === "failed") {
|
|
652
|
+
console.error("⚠️ No browser could be opened here; open the URL above yourself.");
|
|
653
|
+
}
|
|
619
654
|
return 0;
|
|
620
655
|
},
|
|
621
656
|
});
|
|
@@ -782,6 +817,10 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
782
817
|
const { handleComboCommand } = await import("./combo");
|
|
783
818
|
return await handleComboCommand(deps.args.slice(1));
|
|
784
819
|
},
|
|
820
|
+
companion: async deps => {
|
|
821
|
+
const { handleCompanionCommand } = await import("./companion");
|
|
822
|
+
return await handleCompanionCommand(deps.args.slice(1));
|
|
823
|
+
},
|
|
785
824
|
route: async deps => {
|
|
786
825
|
if (deps.args[1] !== "combo" && deps.args[1] !== "policy") {
|
|
787
826
|
console.error("Usage: ocx route <combo|policy> <subcommand>");
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { loadConfig } from "../config";
|
|
12
12
|
import { stripGrokConfig, type GrokInjectResult } from "../grok/inject";
|
|
13
|
-
import { removeDesktop3pStandardPivot } from "../claude/desktop-3p";
|
|
13
|
+
import { inspectDesktop3pConfigLibrary, removeDesktop3pStandardPivot } from "../claude/desktop-3p";
|
|
14
|
+
import {
|
|
15
|
+
applyDesktopFirstParty,
|
|
16
|
+
inspectDesktopFirstParty,
|
|
17
|
+
removeDesktopFirstParty,
|
|
18
|
+
resolveClaudeDesktopMode,
|
|
19
|
+
} from "../claude/desktop-first-party";
|
|
14
20
|
import {
|
|
15
21
|
claudeDesktopIntegrationEnabled,
|
|
16
22
|
grokIntegrationEnabled,
|
|
@@ -35,6 +41,10 @@ export interface EnsureDesiredIntegrationsDeps {
|
|
|
35
41
|
opts?: { hostname?: string },
|
|
36
42
|
) => Promise<GrokInjectResult>;
|
|
37
43
|
removeDesktop3pStandardPivot: typeof removeDesktop3pStandardPivot;
|
|
44
|
+
removeDesktopFirstParty?: typeof removeDesktopFirstParty;
|
|
45
|
+
applyDesktopFirstParty?: typeof applyDesktopFirstParty;
|
|
46
|
+
inspectDesktopFirstParty?: typeof inspectDesktopFirstParty;
|
|
47
|
+
inspectDesktop3pConfigLibrary?: typeof inspectDesktop3pConfigLibrary;
|
|
38
48
|
log?: (message: string) => void;
|
|
39
49
|
error?: (message: string) => void;
|
|
40
50
|
}
|
|
@@ -113,16 +123,44 @@ export async function ensureGrokFenceMatchesDesired(
|
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
/**
|
|
116
|
-
* When Claude Desktop is durably OFF, clear any leftover owned gateway profile
|
|
117
|
-
* ensure/update used to leave Claude-3p residue in place after a
|
|
118
|
-
* (drifted fingerprint), so the Integrations card kept looking applied/stale.
|
|
126
|
+
* When Claude Desktop is durably OFF, clear any leftover owned gateway profile and the
|
|
127
|
+
* first-party settings env. ensure/update used to leave Claude-3p residue in place after a
|
|
128
|
+
* failed disable (drifted fingerprint), so the Integrations card kept looking applied/stale.
|
|
129
|
+
*
|
|
130
|
+
* When it is ON in first-party mode, refresh a stale env (the intercept port follows the
|
|
131
|
+
* public port, so a port change would otherwise leave Claude Code pointed at a dead proxy).
|
|
119
132
|
*/
|
|
120
133
|
export function ensureClaudeDesktopMatchesDesired(
|
|
121
134
|
deps: EnsureDesiredIntegrationsDeps = productionDeps,
|
|
122
135
|
): void {
|
|
123
136
|
const config = deps.loadConfig();
|
|
124
137
|
const { log, error } = io(deps);
|
|
125
|
-
if (claudeDesktopIntegrationEnabled(config))
|
|
138
|
+
if (claudeDesktopIntegrationEnabled(config)) {
|
|
139
|
+
if (resolveClaudeDesktopMode(config) !== "first-party") return;
|
|
140
|
+
const library = (deps.inspectDesktop3pConfigLibrary ?? inspectDesktop3pConfigLibrary)({
|
|
141
|
+
appliedFingerprint: config.claudeCode?.desktopProfile?.appliedFingerprint ?? null,
|
|
142
|
+
});
|
|
143
|
+
if (library.kind === "gateway_ours" || library.kind === "gateway_drifted") {
|
|
144
|
+
// The mode marker and the disk disagree. Replacing a live Desktop profile is an operator
|
|
145
|
+
// action, not something an update hook should do silently.
|
|
146
|
+
error("⚠️ Claude Desktop mode is first-party but a gateway profile is still applied; run `ocx claude desktop apply --first-party` (or `--gateway`) to reconcile.");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const seen = (deps.inspectDesktopFirstParty ?? inspectDesktopFirstParty)(config);
|
|
150
|
+
if (!seen.stale) return;
|
|
151
|
+
const applied = (deps.applyDesktopFirstParty ?? applyDesktopFirstParty)(config);
|
|
152
|
+
if (applied.ok && applied.changed) log(` + Claude Desktop first-party env refreshed (${applied.path})`);
|
|
153
|
+
else if (!applied.ok) error(`⚠️ Claude Desktop first-party env refresh skipped: ${applied.reason}.`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const env = (deps.removeDesktopFirstParty ?? removeDesktopFirstParty)();
|
|
158
|
+
if (env.ok && env.changed) log(" ↩️ Claude Desktop first-party env removed.");
|
|
159
|
+
else if (!env.ok) error(`⚠️ Claude Desktop first-party env cleanup skipped: ${env.reason} (${env.path}).`);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
162
|
+
error(`⚠️ Claude Desktop first-party env cleanup failed: ${detail}.`);
|
|
163
|
+
}
|
|
126
164
|
try {
|
|
127
165
|
const removed = deps.removeDesktop3pStandardPivot({
|
|
128
166
|
appliedFingerprint: config.claudeCode?.desktopProfile?.appliedFingerprint ?? null,
|
package/src/cli/help.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
1
|
import { findCommand } from "./registry";
|
|
5
|
-
|
|
6
|
-
const repoRoot = dirname(fileURLToPath(new URL("../../package.json", import.meta.url)));
|
|
2
|
+
import { packageVersion as readPackageVersion } from "../lib/package-version";
|
|
7
3
|
|
|
8
4
|
/**
|
|
9
5
|
* Version of the `ocx` bundle this process is running from.
|
|
@@ -13,9 +9,7 @@ const repoRoot = dirname(fileURLToPath(new URL("../../package.json", import.meta
|
|
|
13
9
|
* rather than throwing; callers must treat that as "cannot compare", not as a mismatch.
|
|
14
10
|
*/
|
|
15
11
|
export function packageVersion(): string {
|
|
16
|
-
|
|
17
|
-
const parsed = JSON.parse(raw) as { version?: unknown };
|
|
18
|
-
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
12
|
+
return readPackageVersion();
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
export function printVersion(): void {
|
|
@@ -29,9 +23,11 @@ Usage:
|
|
|
29
23
|
ocx setup Interactive setup (alias: init)
|
|
30
24
|
ocx start [--port <port>] [--socks5 [host:port] | --socks5-off]
|
|
31
25
|
Start the proxy; SOCKS5 defaults to 127.0.0.1:10808
|
|
32
|
-
ocx stop
|
|
26
|
+
ocx stop [--json] Stop the proxy AND restore native Codex (plain codex works again)
|
|
33
27
|
ocx restore Restore native Codex without stopping (alias: eject)
|
|
34
28
|
ocx restore back Re-point codex at the running proxy (undo restore)
|
|
29
|
+
ocx restore --remove-codex-provider-table
|
|
30
|
+
Also drop [model_providers.opencodex] that a paginated restore kept
|
|
35
31
|
ocx recover-history --legacy-openai --yes
|
|
36
32
|
Force all user-message opencodex rows to OpenAI (legacy recovery)
|
|
37
33
|
ocx recover-history --ocx-compaction <thread-id> --yes
|
|
@@ -67,6 +63,7 @@ Usage:
|
|
|
67
63
|
ocx health [--json] Check proxy health (exit 0=healthy, 1=not)
|
|
68
64
|
ocx capabilities [--json] List declared capabilities and the API routes they drive
|
|
69
65
|
ocx ready [--json] [--wait [--timeout <s>]] Check post-sync readiness (exit 0 only when ready)
|
|
66
|
+
ocx resolve [--json] Config home, effective port, and liveness (JSON for shells)
|
|
70
67
|
ocx provider <sub> Providers, connectivity, quota, and selected models
|
|
71
68
|
ocx account <sub> Accounts, login/reauth, key pools, and quota controls
|
|
72
69
|
ocx models <sub> Live/custom models, visibility, context, and shadow calls
|
|
@@ -89,6 +86,7 @@ Usage:
|
|
|
89
86
|
ocx grok <sub> Grok Build model selection and apply
|
|
90
87
|
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
|
|
91
88
|
ocx config <sub> Validated configuration show/get/set/import/export
|
|
89
|
+
ocx companion <show|set|reset> Menu-bar and widget companion usage settings
|
|
92
90
|
ocx lab <sub> Read-only Compatibility Lab projection inspection
|
|
93
91
|
ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
|
|
94
92
|
ocx claude desktop [sub] Manage and apply Claude Desktop's four-family profile
|