@bitkyc08/opencodex 2.77.0 → 2.78.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/bin/ocx.mjs +11 -10
- package/gui/dist/assets/App-BtfG5id-.js +54 -0
- package/gui/dist/assets/App-lCIslZ1L.css +1 -0
- package/gui/dist/assets/{Tray-gloIYNaW.js → Tray-DiJpSub0.js} +1 -1
- package/gui/dist/assets/index-Btc3Ogw4.js +94 -0
- package/gui/dist/assets/index-DmQB8D_R.css +1 -0
- package/gui/dist/assets/quota-summary-B_a96nXj.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +19 -2
- package/src/adapters/anthropic/client-identity.ts +13 -4
- package/src/adapters/anthropic/native-client-preamble.ts +31 -0
- package/src/adapters/anthropic/passthrough.ts +103 -25
- package/src/adapters/devin/cloud-direct/chat.ts +7 -2
- package/src/adapters/devin/trajectory.ts +49 -0
- package/src/adapters/devin.ts +13 -0
- package/src/adapters/ollama-native.ts +35 -19
- package/src/claude/intercept/client-hello.ts +103 -0
- package/src/claude/intercept/model-bindings.ts +3 -2
- package/src/claude/intercept/picker-listener.ts +279 -66
- package/src/cli/access-audio-input.ts +84 -0
- package/src/cli/access-audio-live.ts +99 -0
- package/src/cli/access-audio.ts +123 -0
- package/src/cli/access-data-client.ts +127 -0
- package/src/cli/access-data-plane.ts +104 -0
- package/src/cli/access-data-response.ts +76 -0
- package/src/cli/access.ts +65 -2
- package/src/cli/account-api.ts +30 -6
- package/src/cli/account-auth.ts +111 -42
- package/src/cli/account-key-quota.ts +118 -0
- package/src/cli/account-main.ts +11 -4
- package/src/cli/account-next-actions.ts +63 -0
- package/src/cli/account-policy-dto.ts +68 -0
- package/src/cli/account-policy.ts +167 -0
- package/src/cli/account-target.ts +8 -0
- package/src/cli/account.ts +34 -8
- package/src/cli/agent-runtime-settings.ts +130 -0
- package/src/cli/agent-settings.ts +101 -0
- package/src/cli/agent.ts +19 -2
- package/src/cli/alias.ts +7 -2
- package/src/cli/aside-profiles.ts +2 -2
- package/src/cli/capabilities-access-remote.ts +147 -0
- package/src/cli/capabilities-accounts.ts +195 -0
- package/src/cli/capabilities-agents-routing.ts +414 -0
- package/src/cli/capabilities-base.ts +783 -0
- package/src/cli/capabilities-command.ts +1 -0
- package/src/cli/capabilities-integrations.ts +264 -0
- package/src/cli/capabilities-lab.ts +214 -0
- package/src/cli/capabilities-observe-system.ts +662 -0
- package/src/cli/capabilities-provider-models.ts +302 -0
- package/src/cli/capabilities.ts +30 -1199
- package/src/cli/capability-types.ts +54 -0
- package/src/cli/catalog-command-result.ts +57 -0
- package/src/cli/chatgpt-command.ts +25 -2
- package/src/cli/claude-agent-startup-sync.ts +17 -0
- package/src/cli/claude-desktop-profile.ts +100 -0
- package/src/cli/claude-desktop.ts +39 -6
- package/src/cli/codex-shim-autorestore.ts +2 -1
- package/src/cli/codex-shim-readiness.ts +23 -3
- package/src/cli/combo-input.ts +88 -0
- package/src/cli/combo-result.ts +52 -0
- package/src/cli/combo-stats.ts +123 -0
- package/src/cli/combo.ts +37 -14
- package/src/cli/companion-timeline.ts +142 -0
- package/src/cli/companion-usage.ts +146 -0
- package/src/cli/companion.ts +12 -3
- package/src/cli/config-command.ts +24 -8
- package/src/cli/dispatch.ts +52 -79
- package/src/cli/doctor.ts +22 -4
- package/src/cli/help-catalog.ts +31 -14
- package/src/cli/help-recovery.ts +2 -2
- package/src/cli/help.ts +25 -11
- package/src/cli/index.ts +38 -27
- package/src/cli/injection-follow.ts +35 -0
- package/src/cli/inspect.ts +5 -0
- package/src/cli/integration-aside-sync.ts +69 -0
- package/src/cli/integration-cursor.ts +106 -0
- package/src/cli/integration-input.ts +60 -0
- package/src/cli/integration-journal.ts +49 -0
- package/src/cli/integration-plan-dto.ts +156 -0
- package/src/cli/integration-preview.ts +215 -0
- package/src/cli/integrations.ts +24 -18
- package/src/cli/json-input.ts +134 -0
- package/src/cli/link.ts +32 -4
- package/src/cli/local-sync-result.ts +41 -0
- package/src/cli/log-follow.ts +84 -0
- package/src/cli/log-view-filter.ts +146 -0
- package/src/cli/logout-command.ts +44 -0
- package/src/cli/model-picker-ordering.ts +137 -0
- package/src/cli/models-custom-input.ts +107 -0
- package/src/cli/models-custom-runtime.ts +112 -0
- package/src/cli/models-order.ts +194 -0
- package/src/cli/models-runtime-subcommands.ts +2 -0
- package/src/cli/models-runtime.ts +7 -0
- package/src/cli/models.ts +66 -81
- package/src/cli/observe-stream.ts +142 -0
- package/src/cli/observe.ts +75 -34
- package/src/cli/provider-batch.ts +77 -0
- package/src/cli/provider-lifecycle-runtime.ts +170 -0
- package/src/cli/provider-result.ts +124 -0
- package/src/cli/provider-runtime.ts +15 -12
- package/src/cli/provider-settings.ts +143 -0
- package/src/cli/provider.ts +112 -34
- package/src/cli/registry.ts +62 -20
- package/src/cli/remote-workspace-hub.ts +83 -0
- package/src/cli/remote-workspace.ts +6 -1
- package/src/cli/restart-failure.ts +27 -0
- package/src/cli/root.ts +12 -0
- package/src/cli/route-policy-write.ts +137 -0
- package/src/cli/route-policy.ts +8 -0
- package/src/cli/runtime-api.ts +56 -79
- package/src/cli/secret-args.ts +149 -0
- package/src/cli/settings-result.ts +30 -0
- package/src/cli/storage.ts +77 -7
- package/src/cli/system-command.ts +60 -2
- package/src/cli/system-restart-client.ts +20 -3
- package/src/cli/system-settings-parity.ts +83 -0
- package/src/cli/update-restart-candidate.ts +21 -0
- package/src/cli/update-restart-child.ts +81 -0
- package/src/cli/update-restart-home.ts +38 -0
- package/src/cli/update-restart-transport.ts +193 -0
- package/src/cli/update-restart.ts +260 -0
- package/src/cli/usage-model-search.ts +58 -0
- package/src/cli/usage-report.ts +13 -5
- package/src/cli/v2-input.ts +66 -0
- package/src/cli/v2-local-output.ts +37 -0
- package/src/cli/v2-runtime.ts +90 -0
- package/src/cli/v2.ts +102 -144
- package/src/clients/config-export/dsh.ts +21 -0
- package/src/clients/config-export.ts +7 -1
- package/src/codex/catalog/build-entries.ts +1 -1
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/heal-observation.ts +66 -0
- package/src/codex/catalog/publication-observer.ts +19 -0
- package/src/codex/catalog/remote.ts +13 -15
- package/src/codex/catalog/restore.ts +19 -6
- package/src/codex/catalog/retained-sync.ts +58 -10
- package/src/codex/catalog/routed-removal.ts +116 -0
- package/src/codex/catalog/write-audit-contract.ts +21 -0
- package/src/codex/catalog/write-audit.ts +171 -0
- package/src/codex/catalog-self-heal.ts +284 -0
- package/src/codex/catalog-write-serialization.ts +71 -7
- package/src/codex/codex-home-owner.ts +124 -0
- package/src/codex/convergence.ts +55 -15
- package/src/codex/features.ts +8 -1
- package/src/codex/inject/restore.ts +46 -4
- package/src/codex/inject.ts +11 -1
- package/src/codex/internal/catalog-writer.ts +120 -13
- package/src/codex/journal.ts +49 -13
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/quota-types.ts +3 -0
- package/src/codex/quota.ts +13 -3
- package/src/codex/refresh.ts +20 -9
- package/src/codex/runtime.ts +7 -1
- package/src/codex/shim-diagnostics.ts +80 -0
- package/src/codex/shim-fingerprint.ts +7 -2
- package/src/codex/shim-migration.ts +153 -0
- package/src/codex/shim-overlay.ts +509 -0
- package/src/codex/shim-path-resolution.ts +14 -2
- package/src/codex/shim-probe.ts +4 -3
- package/src/codex/shim-state-file.ts +93 -7
- package/src/codex/shim.ts +15 -68
- package/src/codex/sync.ts +24 -4
- package/src/combos/failover.ts +19 -0
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +12 -0
- package/src/config/atomic-write.ts +13 -8
- package/src/config/diagnostics.ts +19 -1
- package/src/config/load-degrade.ts +3 -0
- package/src/config/schema/chatgpt-desktop.ts +22 -0
- package/src/config/schema/config-schema.ts +32 -14
- package/src/config/schema/leaf-validators.ts +3 -2
- package/src/generated/compatibility-version.json +445 -129
- package/src/integrations/merge.ts +13 -5
- package/src/integrations/mutation-plan.ts +46 -3
- package/src/integrations/omp-yaml-source.ts +181 -1
- package/src/integrations/ownership-policy.ts +66 -0
- package/src/integrations/registry.ts +50 -1
- package/src/integrations/state.ts +46 -21
- package/src/integrations/target.ts +25 -2
- package/src/integrations/writer.ts +89 -31
- package/src/lib/package-tree-integrity.ts +42 -1
- package/src/lib/proxy-env.ts +3 -0
- package/src/lib/test-home-guard.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +19 -8
- package/src/oauth/anthropic-routing.ts +17 -12
- package/src/oauth/anthropic.ts +27 -2
- package/src/oauth/index.ts +6 -8
- package/src/oauth/pool-settings-capability.ts +9 -2
- package/src/protocols/plan-snapshot.ts +1 -1
- package/src/protocols/settings.ts +28 -4
- package/src/server/caller-session-identity.ts +32 -0
- package/src/server/claude-messages.ts +11 -4
- package/src/server/index/serve-options.ts +12 -7
- package/src/server/index.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +6 -5
- package/src/server/management/context.ts +8 -1
- package/src/server/management/cursor-integration-routes.ts +3 -3
- package/src/server/management/integration-routes.ts +5 -5
- package/src/server/management/model-routes.ts +3 -3
- package/src/server/management/native-integration-routes.ts +2 -2
- package/src/server/management/oauth-account-routes.ts +96 -30
- package/src/server/management/route-registry.ts +25 -24
- package/src/server/messages-native-eligibility.ts +3 -6
- package/src/server/messages-native-oauth.ts +82 -35
- package/src/server/messages-native.ts +78 -16
- package/src/server/responses/adapter-dispatch.ts +53 -28
- package/src/server/responses/core-auth.ts +10 -4
- package/src/server/responses/core-combo.ts +46 -6
- package/src/service/cli.ts +6 -1
- package/src/service/ownership-mutation-lease.d.mts +26 -0
- package/src/service/ownership-mutation-lease.mjs +76 -1
- package/src/types/config.ts +2 -0
- package/src/types/provider.ts +10 -10
- package/src/update/index.ts +2 -1
- package/src/update/update-failure-guidance.d.mts +6 -0
- package/src/update/update-failure-guidance.mjs +30 -5
- package/gui/dist/assets/App-COdEeqSj.css +0 -1
- package/gui/dist/assets/App-DEpCJEP_.js +0 -54
- package/gui/dist/assets/index-Cd7ittMv.css +0 -1
- package/gui/dist/assets/index-DwJKFJm5.js +0 -94
- package/gui/dist/assets/quota-summary-DGcEJU3h.js +0 -1
|
@@ -14,7 +14,7 @@ const USAGE = `Usage:
|
|
|
14
14
|
ocx config set <dot.path> <json-or-string> [--json]
|
|
15
15
|
ocx config unset <dot.path> [--json]
|
|
16
16
|
ocx config validate [path|-] [--json]
|
|
17
|
-
ocx config export <path|->
|
|
17
|
+
ocx config export <path|-> [--json]
|
|
18
18
|
ocx config import <path|-> --yes [--json]`;
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -205,14 +205,21 @@ function validate(value: unknown): OcxConfig {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
208
|
-
|
|
208
|
+
let outcome = 0;
|
|
209
|
+
const status = await runCliAction(async () => {
|
|
209
210
|
const args = [...argv];
|
|
210
|
-
const action = (args.shift() ?? "show").toLowerCase();
|
|
211
211
|
const wantsJson = takeFlag(args, "--json");
|
|
212
|
+
const source = takeFlag(args, "--source");
|
|
213
|
+
if (args.includes("--json") || args.includes("--source")) throw new CliUsageError("config flags may only be specified once", USAGE);
|
|
214
|
+
const action = (args.shift() ?? "show").toLowerCase();
|
|
215
|
+
if (source && action !== "show") throw new CliUsageError("--source is only supported for config show", USAGE);
|
|
212
216
|
if (action === "show") {
|
|
213
|
-
const source = takeFlag(args, "--source");
|
|
214
217
|
rejectArgs(args, USAGE);
|
|
215
218
|
const diagnostics = readConfigDiagnostics();
|
|
219
|
+
if (diagnostics.source === "fallback" || diagnostics.error) {
|
|
220
|
+
console.error(`Warning: Config at ${getConfigPath()} is invalid or unreadable; defaults are being shown for invalid settings. Run: ocx config validate. Inspect: ocx config show --source.`);
|
|
221
|
+
if (!source) outcome = 1;
|
|
222
|
+
}
|
|
216
223
|
const redacted = redact(diagnostics.config);
|
|
217
224
|
const note = await readRemoteHubConfigNote(diagnostics.config);
|
|
218
225
|
// First key, not last: it has to be read before the empty `providers` map that misled a
|
|
@@ -228,7 +235,12 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
228
235
|
const path = args.shift();
|
|
229
236
|
if (!path) throw new CliUsageError("config path is required", USAGE);
|
|
230
237
|
rejectArgs(args, USAGE);
|
|
231
|
-
const
|
|
238
|
+
const diagnostics = readConfigDiagnostics();
|
|
239
|
+
if (diagnostics.source === "fallback" || diagnostics.error) {
|
|
240
|
+
console.error(`Warning: Config at ${getConfigPath()} is invalid or unreadable; defaults are being shown for invalid settings. Run: ocx config validate. Inspect: ocx config show --source.`);
|
|
241
|
+
outcome = 1;
|
|
242
|
+
}
|
|
243
|
+
const value = redact(getPath(diagnostics.config, path), pathSegments(path).at(-1));
|
|
232
244
|
if (wantsJson || typeof value === "object") console.log(JSON.stringify(value, null, 2));
|
|
233
245
|
else console.log(String(value));
|
|
234
246
|
return;
|
|
@@ -236,7 +248,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
236
248
|
if (action === "set" || action === "unset") {
|
|
237
249
|
const path = args.shift();
|
|
238
250
|
const raw = action === "set" ? args.shift() : undefined;
|
|
239
|
-
if (!path || (action === "set" && raw === undefined)) throw new CliUsageError("config path and value are required", USAGE);
|
|
251
|
+
if (!path || (action === "set" && raw === undefined)) throw new CliUsageError(action === "unset" ? "config path is required" : "config path and value are required", USAGE);
|
|
240
252
|
rejectArgs(args, USAGE);
|
|
241
253
|
// #1835/#1838: the read used to happen OUTSIDE the mutation lock, so a concurrent
|
|
242
254
|
// edit landing between it and the save was reverted by this whole-snapshot write.
|
|
@@ -288,7 +300,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
288
300
|
})();
|
|
289
301
|
printData(result.ok ? { ok: true, source: path ?? getConfigPath() } : result, wantsJson,
|
|
290
302
|
[result.ok ? "Config is valid." : `Config is invalid: ${result.error}`]);
|
|
291
|
-
if (!result.ok)
|
|
303
|
+
if (!result.ok) outcome = 1;
|
|
292
304
|
return;
|
|
293
305
|
}
|
|
294
306
|
if (action === "export") {
|
|
@@ -297,7 +309,10 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
297
309
|
rejectArgs(args, USAGE);
|
|
298
310
|
const content = `${JSON.stringify(readConfigDiagnostics().config, null, 2)}\n`;
|
|
299
311
|
if (path === "-") process.stdout.write(content);
|
|
300
|
-
else {
|
|
312
|
+
else {
|
|
313
|
+
writeFileSync(path, content, { encoding: "utf8", mode: 0o600 });
|
|
314
|
+
printData({ ok: true, path }, wantsJson, [`Exported config to ${path}.`]);
|
|
315
|
+
}
|
|
301
316
|
return;
|
|
302
317
|
}
|
|
303
318
|
if (action === "import") {
|
|
@@ -312,6 +327,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
312
327
|
}
|
|
313
328
|
throw new CliUsageError(`unknown config command ${action}`, USAGE);
|
|
314
329
|
});
|
|
330
|
+
return status || outcome;
|
|
315
331
|
}
|
|
316
332
|
|
|
317
333
|
export const CONFIG_USAGE = USAGE;
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -10,8 +10,7 @@ import type { ProxyRestartStartOutcome } from "./tray-proxy";
|
|
|
10
10
|
* never needs to import the entry module back (no cycle).
|
|
11
11
|
*/
|
|
12
12
|
import { CLI_COMMANDS } from "./registry";
|
|
13
|
-
import {
|
|
14
|
-
import type { CliHead } from "./root";
|
|
13
|
+
import { uninstallArgsError, type CliHead } from "./root";
|
|
15
14
|
import type { ReadyArgs } from "./ready";
|
|
16
15
|
import type { LivenessIo, LiveProxy } from "../server/proxy-liveness";
|
|
17
16
|
import type { OcxConfig } from "../types";
|
|
@@ -300,6 +299,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
300
299
|
return Number(process.exitCode ?? 0);
|
|
301
300
|
},
|
|
302
301
|
uninstall: async deps => {
|
|
302
|
+
const error = uninstallArgsError("uninstall", deps.args);
|
|
303
|
+
if (error) {
|
|
304
|
+
console.error(error);
|
|
305
|
+
return 2;
|
|
306
|
+
}
|
|
303
307
|
await deps.handleUninstall();
|
|
304
308
|
return Number(process.exitCode ?? 0);
|
|
305
309
|
},
|
|
@@ -376,62 +380,8 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
376
380
|
return 0;
|
|
377
381
|
},
|
|
378
382
|
logout: async deps => {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
// `ocx logout --json` called removeCredential("--json"), printed "Logged out of
|
|
382
|
-
// --json." and exited 0 -- a silent false success, the worst outcome for a caller
|
|
383
|
-
// that can only see the exit code.
|
|
384
|
-
//
|
|
385
|
-
// That is not merely a wasted call. `normalizeAuthStore` copies every top-level key
|
|
386
|
-
// it finds, so a hand-edited, legacy, or corrupted auth.json containing a `--json`
|
|
387
|
-
// key would have its active account deleted -- and the key dropped entirely if that
|
|
388
|
-
// was its last account. A flag must never reach the store as a provider name.
|
|
389
|
-
const logoutArgs = deps.args.slice(1);
|
|
390
|
-
const wantsJson = logoutArgs.includes("--json");
|
|
391
|
-
// Any leading dash is an option, not a provider. Matching only `--` left the same defect
|
|
392
|
-
// one dash shorter: `ocx logout -j` treated `-j` as the provider name and, with a `-j` key
|
|
393
|
-
// present in the store, deleted it and exited 0.
|
|
394
|
-
const isOption = (arg: string): boolean => arg.startsWith("-");
|
|
395
|
-
const positionals = logoutArgs.filter(arg => !isOption(arg));
|
|
396
|
-
const unknownFlags = logoutArgs.filter(arg => isOption(arg) && arg !== "--json");
|
|
397
|
-
const name = (positionals[0] ?? "").trim().toLowerCase();
|
|
398
|
-
|
|
399
|
-
// Usage failures exit 2 and touch nothing. A missing provider is a usage error; a
|
|
400
|
-
// provider that simply has no credential is a not-found (4) further down, because the
|
|
401
|
-
// vocabulary distinguishes "you called this wrong" from "the thing is not there".
|
|
402
|
-
//
|
|
403
|
-
// The shape check is `isValidProviderName`, not another dash test. Rejecting a leading
|
|
404
|
-
// ASCII `-` fixed `-j` and still let `logout —json` through with a Unicode dash, which is
|
|
405
|
-
// the same defect a third time: each patch named one spelling instead of the class. The
|
|
406
|
-
// canonical validator states the rule positively -- start and end alphanumeric, internal
|
|
407
|
-
// `._-` allowed -- so `github-copilot` and `google-antigravity` pass while every dash
|
|
408
|
-
// variant, empty string, and reserved name fails. Anything that is not a possible
|
|
409
|
-
// provider id cannot reach the store at all.
|
|
410
|
-
const malformedName = Boolean(name) && !isValidProviderName(name);
|
|
411
|
-
if (unknownFlags.length > 0 || positionals.length > 1 || !name || malformedName) {
|
|
412
|
-
const problem = unknownFlags.length > 0
|
|
413
|
-
? `unknown option ${unknownFlags[0]}`
|
|
414
|
-
: positionals.length > 1 ? "too many arguments"
|
|
415
|
-
: malformedName ? `not a valid provider name: ${name}`
|
|
416
|
-
: "missing provider";
|
|
417
|
-
console.error(`Usage: ocx logout <provider> [--json] (${problem})`);
|
|
418
|
-
return 2;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// The disposition comes from inside the store mutation, not from a read-then-remove
|
|
422
|
-
// preflight. `mutateStore` serializes writes, so a preflight leaves a window where a
|
|
423
|
-
// concurrent logout removes the same account and BOTH callers exit 0 claiming a removal --
|
|
424
|
-
// a false success again, just a narrower one than the flag bug above.
|
|
425
|
-
const { removeCredential } = await import("../oauth/store");
|
|
426
|
-
const outcome = await removeCredential(name);
|
|
427
|
-
if (outcome === "not-found") {
|
|
428
|
-
if (wantsJson) console.log(JSON.stringify({ schemaVersion: 1, ok: false, provider: name, removed: false, reason: "not_found" }, null, 2));
|
|
429
|
-
else console.error(`No stored credential for '${name}'.`);
|
|
430
|
-
return 4;
|
|
431
|
-
}
|
|
432
|
-
if (wantsJson) console.log(JSON.stringify({ schemaVersion: 1, ok: true, provider: name, removed: true }, null, 2));
|
|
433
|
-
else console.log(`Logged out of ${name}.`);
|
|
434
|
-
return 0;
|
|
383
|
+
const { handleLogoutCommand } = await import("./logout-command");
|
|
384
|
+
return handleLogoutCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
|
|
435
385
|
},
|
|
436
386
|
sync: async deps => {
|
|
437
387
|
const syncArgs = deps.args.slice(1);
|
|
@@ -538,7 +488,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
538
488
|
},
|
|
539
489
|
v2: async deps => {
|
|
540
490
|
const { cmdV2 } = await import("./v2");
|
|
541
|
-
return await cmdV2(deps.args.slice(1), {}, async () => (await deps.findLiveProxy())?.port);
|
|
491
|
+
return await cmdV2(deps.args.slice(1), { runtimeApi: { findLiveProxy: deps.findLiveProxy } }, async () => (await deps.findLiveProxy())?.port);
|
|
542
492
|
},
|
|
543
493
|
connect: async deps => {
|
|
544
494
|
const { handleConnectCommand } = await import("./connect");
|
|
@@ -550,7 +500,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
550
500
|
},
|
|
551
501
|
"remote-workspace": async deps => {
|
|
552
502
|
const { runRemoteWorkspaceCommand } = await import("./remote-workspace");
|
|
553
|
-
return await runRemoteWorkspaceCommand(deps.args.slice(1));
|
|
503
|
+
return await runRemoteWorkspaceCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
|
|
554
504
|
},
|
|
555
505
|
disconnect: async deps => {
|
|
556
506
|
const { handleDisconnectCommand } = await import("./connect");
|
|
@@ -570,7 +520,8 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
570
520
|
const cacheGateSnapshot = deps.loadConfig();
|
|
571
521
|
const desiredDisabled = !shouldSyncCodexOnStart(cacheGateSnapshot);
|
|
572
522
|
const invalidated = withCatalogWriteSerialization(owningCodexHome, permit =>
|
|
573
|
-
invalidateCodexModelsCacheWithPermitOutcome(permit, owningCodexHome, { allowWhenDesiredDisabled: true })
|
|
523
|
+
invalidateCodexModelsCacheWithPermitOutcome(permit, owningCodexHome, { allowWhenDesiredDisabled: true }),
|
|
524
|
+
{ intent: "cache", writer: "sync-cache" });
|
|
574
525
|
const cacheJson = cacheArgs.includes("--json");
|
|
575
526
|
const jsonSafeLog = cacheJson
|
|
576
527
|
? { log: (...values: unknown[]) => console.error(...values), error: (...values: unknown[]) => console.error(...values) }
|
|
@@ -633,6 +584,10 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
633
584
|
console.log("No Codex catalog to derive a cache from; nothing to sync.");
|
|
634
585
|
} else if (unchanged) {
|
|
635
586
|
console.log("Codex model cache is already current; nothing to sync.");
|
|
587
|
+
} else if (invalidated.kind === "unavailable"
|
|
588
|
+
&& (invalidated.reason === "foreign-owner" || invalidated.reason === "owner-unknown")) {
|
|
589
|
+
const { FOREIGN_CODEX_HOME_OWNER_MESSAGE, UNKNOWN_CODEX_HOME_OWNER_MESSAGE } = await import("../codex/catalog/routed-removal");
|
|
590
|
+
console.error(invalidated.reason === "foreign-owner" ? FOREIGN_CODEX_HOME_OWNER_MESSAGE : UNKNOWN_CODEX_HOME_OWNER_MESSAGE);
|
|
636
591
|
} else if (!ok) {
|
|
637
592
|
console.error(`Cache refresh did not complete (${invalidated.kind}). The Codex model cache was not rewritten.`);
|
|
638
593
|
}
|
|
@@ -697,19 +652,33 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
697
652
|
switch (deps.args[1]) {
|
|
698
653
|
case "install": {
|
|
699
654
|
const r = installCodexShim();
|
|
700
|
-
const { healthy, summary } = diagnoseCodexShim();
|
|
655
|
+
const { healthy, runnable, active, summary } = diagnoseCodexShim();
|
|
656
|
+
const success = !r.refused && (runnable ?? healthy);
|
|
701
657
|
const { collectCodexShimReadinessWarnings } = await import("./codex-shim-readiness");
|
|
702
|
-
const warnings =
|
|
658
|
+
const warnings = success
|
|
703
659
|
? collectCodexShimReadinessWarnings()
|
|
704
660
|
: [];
|
|
705
|
-
console.log(`${
|
|
661
|
+
console.log(`${success && healthy && warnings.length === 0 ? "✅ " : "⚠️ "}${r.message}`);
|
|
706
662
|
for (const warning of warnings) console.warn(` ${warning}`);
|
|
707
|
-
if (
|
|
708
|
-
|
|
663
|
+
if (success && active !== undefined && active !== true) {
|
|
664
|
+
const { overlayActivationHint } = await import("../codex/shim-overlay");
|
|
665
|
+
console.warn(` ${overlayActivationHint()}`);
|
|
666
|
+
}
|
|
667
|
+
if (!success) console.error(`${r.refused ? "Codex shim installation was refused" : "Codex shim installation is unhealthy"}: ${summary}`);
|
|
668
|
+
return success ? 0 : 1;
|
|
709
669
|
}
|
|
710
|
-
case "status":
|
|
670
|
+
case "status": {
|
|
671
|
+
const extra = deps.args.slice(2);
|
|
672
|
+
if (extra.length > 0) {
|
|
673
|
+
console.error(extra.some(isJsonOption)
|
|
674
|
+
? "ocx codex-shim status does not support --json; use ocx status --json (codexShim)."
|
|
675
|
+
: "ocx codex-shim status does not accept arguments or options.");
|
|
676
|
+
console.error("Usage: ocx codex-shim status");
|
|
677
|
+
return 2;
|
|
678
|
+
}
|
|
711
679
|
console.log(codexShimStatus());
|
|
712
680
|
break;
|
|
681
|
+
}
|
|
713
682
|
case "uninstall":
|
|
714
683
|
case "remove": {
|
|
715
684
|
const r = uninstallCodexShim();
|
|
@@ -731,7 +700,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
731
700
|
}
|
|
732
701
|
const { runUpdate } = await import("../update");
|
|
733
702
|
await runUpdate();
|
|
734
|
-
return 0;
|
|
703
|
+
return Number(process.exitCode ?? 0);
|
|
735
704
|
},
|
|
736
705
|
"__refresh-version": async deps => {
|
|
737
706
|
// Hidden, detached helper spawned by the update prompt to refresh the
|
|
@@ -786,6 +755,10 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
786
755
|
},
|
|
787
756
|
health: async deps => {
|
|
788
757
|
const healthArgs = deps.args.slice(1);
|
|
758
|
+
if (healthArgs.length > 1 || (healthArgs.length === 1 && healthArgs[0] !== "--json")) {
|
|
759
|
+
console.error("Usage: ocx health [--json]\nSee: ocx help health");
|
|
760
|
+
return 2;
|
|
761
|
+
}
|
|
789
762
|
const wantsHealthJson = healthArgs.includes("--json");
|
|
790
763
|
// A proxy that has only just bound can miss a single probe while its event loop
|
|
791
764
|
// is still settling startup work — the same just-started race the stop paths
|
|
@@ -844,7 +817,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
844
817
|
},
|
|
845
818
|
companion: async deps => {
|
|
846
819
|
const { handleCompanionCommand } = await import("./companion");
|
|
847
|
-
return await handleCompanionCommand(deps.args.slice(1));
|
|
820
|
+
return await handleCompanionCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
|
|
848
821
|
},
|
|
849
822
|
route: async deps => {
|
|
850
823
|
if (deps.args[1] !== "combo" && deps.args[1] !== "policy") {
|
|
@@ -869,7 +842,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
869
842
|
},
|
|
870
843
|
observe: async deps => {
|
|
871
844
|
const { handleObserveCommand } = await import("./observe");
|
|
872
|
-
return await handleObserveCommand(deps.args.slice(1));
|
|
845
|
+
return await handleObserveCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
|
|
873
846
|
},
|
|
874
847
|
inspect: async deps => {
|
|
875
848
|
const { handleInspectCommand } = await import("./inspect");
|
|
@@ -877,11 +850,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
877
850
|
},
|
|
878
851
|
logs: async deps => {
|
|
879
852
|
const { handleObserveCommand } = await import("./observe");
|
|
880
|
-
return await handleObserveCommand([deps.command!, ...deps.args.slice(1)]);
|
|
853
|
+
return await handleObserveCommand([deps.command!, ...deps.args.slice(1)], { findLiveProxy: deps.findLiveProxy });
|
|
881
854
|
},
|
|
882
855
|
usage: async deps => {
|
|
883
856
|
const { handleObserveCommand } = await import("./observe");
|
|
884
|
-
return await handleObserveCommand([deps.command!, ...deps.args.slice(1)]);
|
|
857
|
+
return await handleObserveCommand([deps.command!, ...deps.args.slice(1)], { findLiveProxy: deps.findLiveProxy });
|
|
885
858
|
},
|
|
886
859
|
storage: async deps => {
|
|
887
860
|
// `ocx storage` used to be a pure alias of `observe storage`, which reached only the report
|
|
@@ -897,11 +870,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
897
870
|
},
|
|
898
871
|
access: async deps => {
|
|
899
872
|
const { handleAccessCommand } = await import("./access");
|
|
900
|
-
return await handleAccessCommand(deps.args.slice(1));
|
|
873
|
+
return await handleAccessCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
|
|
901
874
|
},
|
|
902
875
|
"api-key": async deps => {
|
|
903
876
|
const { handleAccessCommand } = await import("./access");
|
|
904
|
-
return await handleAccessCommand(["key", ...deps.args.slice(1)]);
|
|
877
|
+
return await handleAccessCommand(["key", ...deps.args.slice(1)], { findLiveProxy: deps.findLiveProxy });
|
|
905
878
|
},
|
|
906
879
|
api: async deps => {
|
|
907
880
|
const { handleApiCommand } = await import("./api-protocols");
|
|
@@ -925,21 +898,21 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
925
898
|
// integrations `client` manages, so they get their own subcommand rather than being
|
|
926
899
|
// folded into one that means something else.
|
|
927
900
|
const { handleIntegrationCommand } = await import("./inspect");
|
|
928
|
-
return await handleIntegrationCommand(deps.args.slice(1));
|
|
901
|
+
return await handleIntegrationCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
|
|
929
902
|
} else if (integration === "claude") {
|
|
930
903
|
const { handleClaudeConfigCommand } = await import("./integrations");
|
|
931
904
|
return await handleClaudeConfigCommand(deps.args.slice(2));
|
|
932
905
|
} else if (integration === "client") {
|
|
933
906
|
const { handleClientIntegrationCommand } = await import("./integrations");
|
|
934
|
-
return await handleClientIntegrationCommand(deps.args.slice(2));
|
|
907
|
+
return await handleClientIntegrationCommand(deps.args.slice(2), { findLiveProxy: deps.findLiveProxy });
|
|
935
908
|
} else {
|
|
936
|
-
console.error("Usage: ocx integration <claude|grok|client> <subcommand
|
|
909
|
+
console.error("Usage: ocx integration <claude|grok|client|native> <subcommand>\nSee: ocx help integration");
|
|
937
910
|
return 2;
|
|
938
911
|
}
|
|
939
912
|
},
|
|
940
913
|
system: async deps => {
|
|
941
914
|
const { handleSystemCommand } = await import("./system-command");
|
|
942
|
-
return await handleSystemCommand(deps.args.slice(1));
|
|
915
|
+
return await handleSystemCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
|
|
943
916
|
},
|
|
944
917
|
config: async deps => {
|
|
945
918
|
const { handleConfigCommand } = await import("./config-command");
|
|
@@ -958,7 +931,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
958
931
|
// "ocx claude desktop" → write Desktop 3P config
|
|
959
932
|
if (deps.args[1] === "desktop") {
|
|
960
933
|
const { handleClaudeDesktopCommand } = await import("./claude-desktop");
|
|
961
|
-
const exitCode = await handleClaudeDesktopCommand(deps.args.slice(2));
|
|
934
|
+
const exitCode = await handleClaudeDesktopCommand(deps.args.slice(2), { findLiveProxyImpl: deps.findLiveProxy });
|
|
962
935
|
if (exitCode !== 0) return exitCode;
|
|
963
936
|
return 0;
|
|
964
937
|
}
|
package/src/cli/doctor.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../codex/subagent-model-fallback";
|
|
34
34
|
import { readCatalog, readCodexCatalogPath, readConfiguredDefaultModel } from "../codex/catalog/parsing";
|
|
35
35
|
import { diagnoseCodexShim, findCodexOnPath, isWindowsInteropDir, type CodexShimDiagnostic } from "../codex/shim";
|
|
36
|
+
import { overlayActivationHint } from "../codex/shim-overlay";
|
|
36
37
|
import { providerTableString, rootTomlString } from "../codex/injected-marker";
|
|
37
38
|
import { countPendingOpencodexHistory } from "../codex/history-provider";
|
|
38
39
|
import {
|
|
@@ -481,28 +482,41 @@ export function collectProviderApiKeyDiagnostics(
|
|
|
481
482
|
|
|
482
483
|
export type CodexEnvKeyReadinessDiagnostic = {
|
|
483
484
|
envName: string;
|
|
484
|
-
shimState: "missing" | "unhealthy";
|
|
485
|
+
shimState: "missing" | "unhealthy" | "inactive";
|
|
485
486
|
detail: string;
|
|
486
487
|
action: string;
|
|
487
488
|
};
|
|
488
489
|
|
|
490
|
+
/** Shell activation and legacy migration are independent of admission-token readiness. */
|
|
491
|
+
export function formatCodexShimDoctorLines(shim: CodexShimDiagnostic, platform = process.platform): string[] {
|
|
492
|
+
if (platform === "win32" || !shim.installed) return [];
|
|
493
|
+
const inactiveOverlay = shim.runnable === true && shim.active === false;
|
|
494
|
+
const healthyLegacy = shim.healthy && shim.runnable === undefined;
|
|
495
|
+
return inactiveOverlay || healthyLegacy ? shim.summary.split("\n").map(line => ` ${line}`) : [];
|
|
496
|
+
}
|
|
497
|
+
|
|
489
498
|
/** Warn when routed Codex cannot obtain its configured admission token at launch. */
|
|
490
499
|
export function collectCodexEnvKeyReadiness(
|
|
491
500
|
configText: string | null,
|
|
492
501
|
env: EnvMap,
|
|
493
502
|
shim: CodexShimDiagnostic,
|
|
494
503
|
serviceTokenPresent: boolean,
|
|
504
|
+
shimActivationShown = false,
|
|
495
505
|
): CodexEnvKeyReadinessDiagnostic | null {
|
|
496
506
|
if (!configText || rootTomlString(configText, "model_provider") !== "opencodex") return null;
|
|
497
507
|
const envName = providerTableString(configText, "opencodex", "env_key")?.trim();
|
|
498
508
|
const envValue = envName ? ownEnvValue(env, envName) : undefined;
|
|
499
509
|
if (!envName || envValue?.trim() || shim.healthy || !serviceTokenPresent) return null;
|
|
500
|
-
const shimState = shim.installed ? "unhealthy" : "missing";
|
|
510
|
+
const shimState = shim.runnable && shim.active === false ? "inactive" : shim.installed ? "unhealthy" : "missing";
|
|
501
511
|
return {
|
|
502
512
|
envName,
|
|
503
513
|
shimState,
|
|
504
514
|
detail: `Codex uses env_key ${envName}, but that variable is unset and the OpenCodex shim is ${shimState}; the service token file exists but plain Codex does not load it`,
|
|
505
|
-
action:
|
|
515
|
+
action: shimState === "inactive"
|
|
516
|
+
? shimActivationShown
|
|
517
|
+
? `Activate the PATH shim as shown under Codex restart safety, or export ${envName} in the process that starts Codex`
|
|
518
|
+
: `${overlayActivationHint()} Or export ${envName} in the process that starts Codex`
|
|
519
|
+
: `Run 'ocx codex-shim install' to repair launch-time token injection, or export ${envName} in the process that starts Codex`,
|
|
506
520
|
};
|
|
507
521
|
}
|
|
508
522
|
|
|
@@ -1349,11 +1363,14 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1349
1363
|
try { return readFileSync(codexConfigPath, "utf8"); } catch { return null; }
|
|
1350
1364
|
})();
|
|
1351
1365
|
const serviceTokenPresent = Boolean(readInstalledServiceToken()?.trim());
|
|
1366
|
+
const codexShim = diagnoseCodexShim();
|
|
1367
|
+
const codexShimLines = formatCodexShimDoctorLines(codexShim);
|
|
1352
1368
|
const codexEnvKeyReadiness = collectCodexEnvKeyReadiness(
|
|
1353
1369
|
codexConfigText,
|
|
1354
1370
|
process.env,
|
|
1355
|
-
|
|
1371
|
+
codexShim,
|
|
1356
1372
|
serviceTokenPresent,
|
|
1373
|
+
codexShimLines.length > 0,
|
|
1357
1374
|
);
|
|
1358
1375
|
// Use the same attested live startup verdict as `ocx status` when the proxy is already
|
|
1359
1376
|
// identity-verified. A shell-local systemd probe can be a false negative for a system-wide
|
|
@@ -1366,6 +1383,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1366
1383
|
console.log("\nCodex restart safety");
|
|
1367
1384
|
console.log(` ${startup.rebootSafe ? "ok " : "!! "} ${startupHealthSummary(startup)}`);
|
|
1368
1385
|
console.log(` ${formatStartupRoutingDetail(startup)}`);
|
|
1386
|
+
for (const line of codexShimLines) console.log(line);
|
|
1369
1387
|
|
|
1370
1388
|
console.log("\nCodex runtime selection");
|
|
1371
1389
|
{
|
package/src/cli/help-catalog.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { CAPABILITIES, type Capability } from "./capabilities";
|
|
|
2
2
|
import { CLI_COMMANDS, findCommand, type CliCommandEntry } from "./registry";
|
|
3
3
|
|
|
4
4
|
export type HelpResolution =
|
|
5
|
-
| { kind: "entry"; entry: CliCommandEntry; canonicalName: string; children: readonly Capability[] }
|
|
6
|
-
| { kind: "capability"; path: string[]; capability: Capability }
|
|
5
|
+
| { kind: "entry"; entry: CliCommandEntry; canonicalName: string; capability?: Capability; children: readonly Capability[] }
|
|
6
|
+
| { kind: "capability"; path: string[]; capability: Capability; children: readonly Capability[] }
|
|
7
7
|
| { kind: "prefix"; path: string[]; children: readonly Capability[] }
|
|
8
8
|
| { kind: "models-context"; path: string[] }
|
|
9
9
|
| { kind: "unavailable"; path: string[]; parent?: string[] };
|
|
@@ -22,6 +22,18 @@ function canonicalEntry(entry: CliCommandEntry): CliCommandEntry {
|
|
|
22
22
|
return CLI_COMMANDS.find(candidate => candidate.aliases?.includes(entry.name)) ?? entry;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Help-only aliases from access dispatch; never rewrite execution argv or operands. */
|
|
26
|
+
function canonicalHelpPath(requested: readonly string[], entry: CliCommandEntry): string[] {
|
|
27
|
+
const path = entry.name === "api-key"
|
|
28
|
+
? ["access", "key", ...requested.slice(1)]
|
|
29
|
+
: [canonicalEntry(entry).name, ...requested.slice(1)];
|
|
30
|
+
if (path[0] === "access") {
|
|
31
|
+
if (path[1] === "keys") path[1] = "key";
|
|
32
|
+
if (path[1] === "key" && path[2] === "delete") path[2] = "remove";
|
|
33
|
+
}
|
|
34
|
+
return path;
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
export interface HelpRecoveryCandidate {
|
|
26
38
|
names: string[];
|
|
27
39
|
path: string[];
|
|
@@ -35,19 +47,24 @@ export function helpRecoveryCandidates(parent: readonly string[] = []): HelpReco
|
|
|
35
47
|
if (entry.hidden) continue;
|
|
36
48
|
const canonical = canonicalEntry(entry);
|
|
37
49
|
if (canonical.hidden) continue;
|
|
38
|
-
const
|
|
50
|
+
const path = canonicalHelpPath([entry.name], entry);
|
|
51
|
+
const identity = path.join(" ");
|
|
52
|
+
const candidate = candidates.get(identity) ?? { names: [], path };
|
|
39
53
|
candidate.names = [...new Set([...candidate.names, entry.name, ...(entry.aliases ?? [])])];
|
|
40
|
-
candidates.set(
|
|
54
|
+
candidates.set(identity, candidate);
|
|
41
55
|
}
|
|
42
56
|
} else {
|
|
43
|
-
if (parent.length >= MAX_DOCUMENTED_DEPTH) return [];
|
|
44
57
|
const entry = findCommand(parent[0]);
|
|
45
58
|
if (!entry || entry.hidden) return [];
|
|
46
|
-
const prefix =
|
|
59
|
+
const prefix = canonicalHelpPath(parent, entry);
|
|
60
|
+
if (prefix.length >= MAX_DOCUMENTED_DEPTH) return [];
|
|
47
61
|
for (const documented of DOCUMENTED_PATHS) {
|
|
48
62
|
if (documented.length <= prefix.length || !startsWithPath(documented, prefix)) continue;
|
|
49
63
|
const path = documented.slice(0, prefix.length + 1);
|
|
50
|
-
|
|
64
|
+
const names = [path[path.length - 1]];
|
|
65
|
+
if (path.join(" ") === "access key") names.push("keys");
|
|
66
|
+
if (path.join(" ") === "access key remove") names.push("delete");
|
|
67
|
+
candidates.set(path.join(" "), { names, path });
|
|
51
68
|
}
|
|
52
69
|
}
|
|
53
70
|
return [...candidates.values()];
|
|
@@ -58,19 +75,19 @@ export function resolveHelpPath(requested: readonly string[]): HelpResolution {
|
|
|
58
75
|
const entry = requested[0] ? findCommand(requested[0]) : undefined;
|
|
59
76
|
if (!entry) return { kind: "unavailable", path: [...requested] };
|
|
60
77
|
// Keep exact-name alias entries for root help; nested topics use the owner.
|
|
61
|
-
const
|
|
78
|
+
const path = canonicalHelpPath(requested, entry);
|
|
62
79
|
const declared = CAPABILITIES.filter(capability => !findCommand(capability.command[0])?.hidden);
|
|
63
80
|
if (requested.length === 1) return {
|
|
64
|
-
kind: "entry", entry, canonicalName:
|
|
65
|
-
|
|
81
|
+
kind: "entry", entry, canonicalName: path.join(" "),
|
|
82
|
+
capability: entry.hidden ? undefined : declared.find(candidate => candidate.command.length === path.length && startsWithPath(candidate.command, path)),
|
|
83
|
+
children: entry.hidden ? [] : declared.filter(candidate => candidate.command.length > path.length && startsWithPath(candidate.command, path)),
|
|
66
84
|
};
|
|
67
|
-
const path = [canonical.name, ...requested.slice(1)];
|
|
68
85
|
if (path.length === 2 && path[0] === "models" && path[1] === "context") {
|
|
69
86
|
return { kind: "models-context", path };
|
|
70
87
|
}
|
|
71
88
|
const capability = declared.find(candidate => candidate.command.length === path.length && startsWithPath(candidate.command, path));
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
const children = declared.filter(candidate => candidate.command.length > path.length && startsWithPath(candidate.command, path));
|
|
90
|
+
if (capability) return { kind: "capability", path, capability, children };
|
|
74
91
|
if (children.length) return { kind: "prefix", path, children };
|
|
75
92
|
|
|
76
93
|
for (let length = Math.min(path.length - 1, MAX_DOCUMENTED_DEPTH); length > 1; length--) {
|
|
@@ -80,5 +97,5 @@ export function resolveHelpPath(requested: readonly string[]): HelpResolution {
|
|
|
80
97
|
return { kind: "unavailable", path, parent };
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
|
-
return { kind: "unavailable", path, parent: [
|
|
100
|
+
return { kind: "unavailable", path, parent: [path[0]] };
|
|
84
101
|
}
|
package/src/cli/help-recovery.ts
CHANGED
|
@@ -35,9 +35,9 @@ function distance(left: string, right: string, limit: number): number {
|
|
|
35
35
|
export function suggestHelpPaths(path: readonly string[]): string[][] {
|
|
36
36
|
if (!path.length || path.length > MAX_PATH_DEPTH || !path.every(safeToken)) return [];
|
|
37
37
|
const result = resolveHelpPath(path);
|
|
38
|
-
if (result.kind !== "unavailable") return [];
|
|
38
|
+
if (result.kind !== "unavailable" || result.path.length > MAX_PATH_DEPTH) return [];
|
|
39
39
|
const parent = result.parent ?? [];
|
|
40
|
-
const token = path[parent.length].toLowerCase();
|
|
40
|
+
const token = result.path[parent.length].toLowerCase();
|
|
41
41
|
const limit = token.length < 6 ? 1 : 2;
|
|
42
42
|
return helpRecoveryCandidates(parent).map(candidate => ({
|
|
43
43
|
path: candidate.path,
|
package/src/cli/help.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveHelpPath } from "./help-catalog";
|
|
2
|
+
import type { Capability } from "./capabilities";
|
|
2
3
|
import { MODELS_CONTEXT_DETAILS, MODELS_CONTEXT_USAGE } from "./help-models-context";
|
|
3
4
|
import { renderRootHelp } from "./help-navigation";
|
|
4
5
|
import { formatHelpRecovery } from "./help-recovery";
|
|
@@ -40,7 +41,7 @@ Usage:
|
|
|
40
41
|
ocx recover-history --ocx-compaction <thread-id> --yes
|
|
41
42
|
Back up and make one ocx1-compacted thread replayable by native Codex
|
|
42
43
|
ocx uninstall Remove service/shim/config and restore native Codex (alias: remove)
|
|
43
|
-
ocx service [sub] Run as a background service (default: install
|
|
44
|
+
ocx service [sub] Run as a background service (default: install if absent, otherwise repair)
|
|
44
45
|
ocx codex-shim <sub> Auto-start proxy when \`codex\` launches (install|status|uninstall|remove)
|
|
45
46
|
ocx tray <sub> Windows status tray (install|start|stop|status|uninstall)
|
|
46
47
|
ocx ensure Ensure the proxy is running and Codex config/cache are current
|
|
@@ -94,9 +95,9 @@ Usage:
|
|
|
94
95
|
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
|
|
95
96
|
ocx grok <sub> Grok Build model selection and apply
|
|
96
97
|
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
|
|
97
|
-
ocx config
|
|
98
|
+
ocx config [sub] Validated configuration show/get/set/import/export
|
|
98
99
|
ocx companion <show|set|reset> Menu-bar and widget companion usage settings
|
|
99
|
-
ocx lab <sub>
|
|
100
|
+
ocx lab <sub> Inspect Lab evidence and control local automation
|
|
100
101
|
ocx chatgpt <sub> Experimental app-server shim: launch|restore|status (macOS)
|
|
101
102
|
ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
|
|
102
103
|
ocx claude desktop [sub] Manage and apply Claude Desktop's four-family profile
|
|
@@ -122,6 +123,17 @@ export function hasHelpFlag(values: string[]): boolean {
|
|
|
122
123
|
return values.some(value => value === "--help" || value === "-h" || value === "help");
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
function printCapabilityDetails(capability: Capability, write: (text: string) => void, shown: readonly string[] = []): void {
|
|
127
|
+
if (capability.flags.length) {
|
|
128
|
+
write("\nDeclared flags:");
|
|
129
|
+
for (const flag of capability.flags) {
|
|
130
|
+
write(` ${flag.name}${flag.value && flag.value !== "boolean" ? ` <${flag.value}>` : ""}${flag.required ? " (required)" : ""} ${flag.summary}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const details = capability.details?.filter(detail => !shown.includes(detail));
|
|
134
|
+
if (details?.length) write(`\n${details.join("\n")}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
export function printSubcommandUsage(
|
|
126
138
|
name: string | undefined,
|
|
127
139
|
path?: readonly string[],
|
|
@@ -142,6 +154,7 @@ export function printSubcommandUsage(
|
|
|
142
154
|
if (result.kind === "entry") {
|
|
143
155
|
write(`Usage: ${result.entry.usage}\n\n${result.entry.summary}`);
|
|
144
156
|
if (result.entry.details?.length) write(`\n${result.entry.details.join("\n")}`);
|
|
157
|
+
if (result.capability) printCapabilityDetails(result.capability, write, result.entry.details);
|
|
145
158
|
if (result.children.length) {
|
|
146
159
|
write("\nDeclared commands (incomplete):");
|
|
147
160
|
for (const child of result.children) write(` ocx help ${child.command.join(" ")} ${child.summary}`);
|
|
@@ -154,15 +167,16 @@ export function printSubcommandUsage(
|
|
|
154
167
|
write(`Usage:\n${MODELS_CONTEXT_USAGE}\n\n${MODELS_CONTEXT_DETAILS.join("\n")}`);
|
|
155
168
|
} else if (result.kind === "capability") {
|
|
156
169
|
const { capability } = result;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
170
|
+
const heading = capability.usage !== undefined ? `Usage: ${capability.usage}` : `Command: ocx ${result.path.join(" ")}`;
|
|
171
|
+
write(`${heading}\n\n${capability.summary}`);
|
|
172
|
+
printCapabilityDetails(capability, write);
|
|
173
|
+
if (result.children.length) {
|
|
174
|
+
write("\nDeclared commands (incomplete):");
|
|
175
|
+
for (const child of result.children) write(` ocx help ${child.command.join(" ")} ${child.summary}`);
|
|
176
|
+
}
|
|
177
|
+
if (capability.usage === undefined) {
|
|
178
|
+
write("\nCapability metadata is incomplete; this is not the full operand grammar.");
|
|
163
179
|
}
|
|
164
|
-
if (capability.details?.length) write(`\n${capability.details.join("\n")}`);
|
|
165
|
-
write("\nCapability metadata is incomplete; this is not the full operand grammar.");
|
|
166
180
|
} else {
|
|
167
181
|
write(`Command group: ocx ${result.path.join(" ")}\n\nDeclared commands (incomplete):`);
|
|
168
182
|
for (const child of result.children) write(` ocx ${child.command.join(" ")} ${child.summary}`);
|