@bitkyc08/opencodex 2.36.0 → 2.38.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.
Files changed (70) hide show
  1. package/bin/ocx.mjs +69 -10
  2. package/gui/dist/assets/index-C14iCj_Q.js +112 -0
  3. package/gui/dist/assets/index-D7PIz7_g.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/gui/dist/provider-icons/aside.svg +3 -0
  6. package/gui/dist/provider-icons/deepseek-harness.svg +3 -0
  7. package/gui/dist/provider-icons/oh-my-pi.svg +11 -0
  8. package/gui/dist/provider-icons/openclaw.svg +54 -0
  9. package/gui/dist/provider-icons/prime-agent.svg +21 -0
  10. package/gui/dist/provider-icons/zcode.svg +219 -0
  11. package/package.json +1 -1
  12. package/src/adapters/cursor/protobuf-request.ts +4 -1
  13. package/src/adapters/cursor/tool-definitions.ts +36 -4
  14. package/src/adapters/kiro-constants.ts +36 -2
  15. package/src/adapters/kiro.ts +13 -2
  16. package/src/cli/capabilities.ts +14 -0
  17. package/src/cli/claude.ts +12 -0
  18. package/src/cli/codex-cli-update.ts +96 -0
  19. package/src/cli/codex-shim-autorestore.ts +3 -0
  20. package/src/cli/export-command.ts +18 -17
  21. package/src/cli/help.ts +2 -2
  22. package/src/cli/index.ts +3 -2
  23. package/src/cli/launcher-context.ts +53 -2
  24. package/src/cli/opencode.ts +126 -33
  25. package/src/cli/registry.ts +16 -10
  26. package/src/cli/system-command.ts +6 -1
  27. package/src/clients/config-export.ts +293 -28
  28. package/src/codex/account-store.ts +10 -4
  29. package/src/codex/autostart-health.ts +3 -3
  30. package/src/codex/catalog/provider-fetch.ts +20 -1
  31. package/src/codex/catalog/sync.ts +4 -3
  32. package/src/codex/cli-install-provenance.ts +795 -0
  33. package/src/codex/convergence.ts +4 -3
  34. package/src/codex/credential-mutation-epoch.ts +11 -0
  35. package/src/codex/main-account.ts +2 -0
  36. package/src/codex/model-entitlements.ts +489 -30
  37. package/src/codex/native-profile-manager.ts +4 -0
  38. package/src/codex/reset-credit-operation-ledger.ts +1411 -0
  39. package/src/codex/reset-credit-recovery.ts +20 -2
  40. package/src/codex/shim.ts +204 -18
  41. package/src/codex/user-identity.ts +2 -1
  42. package/src/config/paths.ts +18 -3
  43. package/src/config.ts +23 -0
  44. package/src/generated/compatibility-version.json +84 -48
  45. package/src/integrations/registry.ts +112 -0
  46. package/src/integrations/state.ts +67 -5
  47. package/src/integrations/writer.ts +25 -9
  48. package/src/lib/bounded-subprocess.ts +36 -0
  49. package/src/lib/strict-semver.ts +47 -0
  50. package/src/lib/windows-elevation.ts +32 -1
  51. package/src/lib/windows-secret-acl.ts +47 -25
  52. package/src/lib/windows-service-mutation-lock.ts +133 -0
  53. package/src/lib/windows-user-principal.ts +15 -17
  54. package/src/responses/spill-store.ts +334 -29
  55. package/src/responses/state.ts +488 -7
  56. package/src/server/index.ts +4 -3
  57. package/src/server/lifecycle.ts +5 -1
  58. package/src/server/management/model-rows.ts +11 -2
  59. package/src/server/management/provider-routes.ts +4 -0
  60. package/src/server/management/system-restart.ts +5 -5
  61. package/src/server/management-api.ts +7 -2
  62. package/src/server/startup-action-control.ts +3 -2
  63. package/src/service.ts +594 -33
  64. package/src/sidecar/candidates.ts +1 -1
  65. package/src/update/codex-cli-update-launch-policy.d.mts +18 -0
  66. package/src/update/codex-cli-update-launch-policy.mjs +30 -0
  67. package/src/update/index.ts +3 -2
  68. package/src/update/job.ts +10 -11
  69. package/gui/dist/assets/index-Cy7Z_pl0.css +0 -1
  70. package/gui/dist/assets/index-DO8liQVL.js +0 -112
@@ -14,9 +14,19 @@ export const ANTHROPIC_PARENT_ENV_SLOTS = [
14
14
  ] as const;
15
15
 
16
16
  export type AnthropicParentEnvSlot = typeof ANTHROPIC_PARENT_ENV_SLOTS[number];
17
+ export type CodexCliVersionManagerRootEnvSlot = typeof CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS[number];
18
+
19
+ type CodexCliVersionManagerRoots = Readonly<Partial<Record<CodexCliVersionManagerRootEnvSlot, string>>>;
17
20
 
18
21
  export type TrustedNodeLaunchContext = {
19
22
  anthropicEnvSlots: readonly AnthropicParentEnvSlot[];
23
+ codexCliInspectionEnv: Readonly<{
24
+ codexCliPath: string | null;
25
+ path: string | null;
26
+ pathExt: string | null;
27
+ managerRoots: CodexCliVersionManagerRoots | null;
28
+ configDir: string;
29
+ }> | null;
20
30
  };
21
31
 
22
32
  let trustedContext: TrustedNodeLaunchContext | null = null;
@@ -25,6 +35,21 @@ function isLaunchProof(value: string): boolean {
25
35
  return /^[A-Za-z0-9_-]{43}$/.test(value);
26
36
  }
27
37
 
38
+ function parseVersionManagerRoots(value: unknown): CodexCliVersionManagerRoots | null | undefined {
39
+ // Missing means an older launcher. Preserve compatibility for unrelated
40
+ // commands, but updater inspection treats the incomplete snapshot as
41
+ // untrusted and fails closed.
42
+ if (value === undefined) return null;
43
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
44
+ const allowed = new Set<string>(CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS);
45
+ const entries = Object.entries(value);
46
+ if (entries.length > allowed.size || entries.some(([name, root]) =>
47
+ !allowed.has(name) || typeof root !== "string" || root.length === 0 || root.length > 32 * 1024)) {
48
+ return undefined;
49
+ }
50
+ return Object.freeze(Object.fromEntries(entries)) as CodexCliVersionManagerRoots;
51
+ }
52
+
28
53
  /** Consume the internal proof before normal CLI argument parsing. */
29
54
  export function initializeNodeLauncherContext(
30
55
  argv: string[] = process.argv,
@@ -45,7 +70,7 @@ export function initializeNodeLauncherContext(
45
70
  delete env.OCX_PRE_BUN_ANTHROPIC_ENV;
46
71
  trustedContext = null;
47
72
 
48
- if (proofArgs.length !== 1 || !raw || raw.length > 2048) return null;
73
+ if (proofArgs.length !== 1 || !raw || raw.length > 64 * 1024) return null;
49
74
  const proof = proofArgs[0]!;
50
75
  if (!isLaunchProof(proof)) return null;
51
76
 
@@ -54,6 +79,7 @@ export function initializeNodeLauncherContext(
54
79
  version?: unknown;
55
80
  proof?: unknown;
56
81
  anthropicEnvSlots?: unknown;
82
+ codexCliInspectionEnv?: unknown;
57
83
  };
58
84
  if (parsed.version !== 1 || parsed.proof !== proof || !Array.isArray(parsed.anthropicEnvSlots)) {
59
85
  return null;
@@ -65,7 +91,31 @@ export function initializeNodeLauncherContext(
65
91
  if (slots.length !== parsed.anthropicEnvSlots.length || new Set(slots).size !== slots.length) {
66
92
  return null;
67
93
  }
68
- trustedContext = { anthropicEnvSlots: slots };
94
+ const inspection = parsed.codexCliInspectionEnv;
95
+ const managerRoots = inspection && typeof inspection === "object" && !Array.isArray(inspection)
96
+ ? parseVersionManagerRoots((inspection as Record<string, unknown>).managerRoots)
97
+ : null;
98
+ const codexCliInspectionEnv = inspection === null || inspection === undefined
99
+ ? null
100
+ : inspection && typeof inspection === "object" && !Array.isArray(inspection)
101
+ && ["codexCliPath", "path", "pathExt"].every(key => {
102
+ const value = (inspection as Record<string, unknown>)[key];
103
+ return value === null || typeof value === "string";
104
+ })
105
+ && typeof (inspection as Record<string, unknown>).configDir === "string"
106
+ && (inspection as Record<string, string>).configDir.length > 0
107
+ && (inspection as Record<string, string>).configDir.length <= 32 * 1024
108
+ && managerRoots !== undefined
109
+ ? Object.freeze({
110
+ codexCliPath: (inspection as Record<string, string | null>).codexCliPath ?? null,
111
+ path: (inspection as Record<string, string | null>).path ?? null,
112
+ pathExt: (inspection as Record<string, string | null>).pathExt ?? null,
113
+ managerRoots,
114
+ configDir: (inspection as Record<string, string>).configDir,
115
+ })
116
+ : undefined;
117
+ if (codexCliInspectionEnv === undefined) return null;
118
+ trustedContext = { anthropicEnvSlots: slots, codexCliInspectionEnv };
69
119
  return trustedContext;
70
120
  } catch {
71
121
  return null;
@@ -75,3 +125,4 @@ export function initializeNodeLauncherContext(
75
125
  export function trustedNodeLauncherContext(): TrustedNodeLaunchContext | null {
76
126
  return trustedContext;
77
127
  }
128
+ import { CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS } from "../update/codex-cli-update-launch-policy.mjs";
@@ -27,12 +27,17 @@ import {
27
27
  OPENCODE_PROVIDER_ID,
28
28
  buildOpencodeProviderBlockFromCatalog,
29
29
  opencodeGlobalConfigPath,
30
+ opencodeProviderBlocks,
31
+ opencodeProxyBaseUrl,
32
+ opencodeV2ProviderBlock,
30
33
  } from "../clients/config-export";
31
34
  import type {
32
35
  OpencodeCatalogModel,
33
36
  OpencodeGeneratedConfig,
34
37
  OpencodeLaunchEnv,
35
38
  OpencodeProviderBlock,
39
+ OpencodeProviderBlocks,
40
+ OpencodeV2ProviderBlock,
36
41
  } from "../clients/config-export";
37
42
  import { visibleNativeSlugs } from "../codex/catalog";
38
43
  import { commandInvocation } from "../lib/win-exec";
@@ -57,6 +62,7 @@ export {
57
62
  buildOpencodeProviderBlockFromCatalog,
58
63
  opencodeGlobalConfigPath,
59
64
  opencodeProxyBaseUrl,
65
+ opencodeV2ProviderBlock,
60
66
  } from "../clients/config-export";
61
67
  export type {
62
68
  OpencodeCatalogModel,
@@ -64,6 +70,8 @@ export type {
64
70
  OpencodeLaunchEnv,
65
71
  OpencodeModelEntry,
66
72
  OpencodeProviderBlock,
73
+ OpencodeProviderBlocks,
74
+ OpencodeV2ProviderBlock,
67
75
  } from "../clients/config-export";
68
76
 
69
77
  /** One proxy-routed model destined for the generated provider block. */
@@ -74,6 +82,8 @@ export interface OpencodeRoutedModel {
74
82
  contextWindow?: number;
75
83
  /** Authoritative display label (CatalogModel.displayName); optional. */
76
84
  displayName?: string;
85
+ /** Declared effort ladder; exported as opencode model variants when present. */
86
+ reasoningEfforts?: readonly string[];
77
87
  }
78
88
 
79
89
  /** Row shape from authenticated GET /api/models on the running proxy. */
@@ -85,6 +95,10 @@ export interface OpencodeProxyModelRow {
85
95
  disabled?: boolean;
86
96
  displayName?: string;
87
97
  contextWindow?: number;
98
+ /** Declared effort ladder from `/api/models`; carried into opencode model variants. */
99
+ reasoningEfforts?: string[];
100
+ /** Declared default effort from `/api/models`. */
101
+ defaultReasoningEffort?: string;
88
102
  }
89
103
 
90
104
  const PROJECT_CONFIG_FILENAMES = ["opencode.json", "opencode.jsonc"] as const;
@@ -196,16 +210,13 @@ export function opencodeLaunchNativeSlugs(config: OcxConfig): string[] {
196
210
  return [...visibleNativeSlugs(config)];
197
211
  }
198
212
 
199
- /** Back-compat helper for unit tests that assemble slugs/routed rows directly. */
200
- export function buildOpencodeProviderBlock(
201
- port: number,
213
+ /** Catalog rows for the slugs/routed models a caller assembled by hand. */
214
+ function opencodeLaunchCatalog(
202
215
  nativeSlugs: readonly string[],
203
216
  routedModels: readonly OpencodeRoutedModel[],
204
- nativeContextWindow: (slug: string) => number | undefined = () => undefined,
205
- hostname?: string,
206
- config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
207
- ): OpencodeProviderBlock {
208
- const catalog: OpencodeCatalogModel[] = [
217
+ nativeContextWindow: (slug: string) => number | undefined,
218
+ ): OpencodeCatalogModel[] {
219
+ return [
209
220
  ...nativeSlugs.map(id => ({
210
221
  namespaced: id,
211
222
  native: true,
@@ -220,9 +231,71 @@ export function buildOpencodeProviderBlock(
220
231
  id: model.id,
221
232
  contextWindow: model.contextWindow,
222
233
  displayName: model.displayName,
234
+ ...(model.reasoningEfforts && model.reasoningEfforts.length > 0
235
+ ? { reasoningEfforts: [...model.reasoningEfforts] }
236
+ : {}),
223
237
  })),
224
238
  ];
225
- return buildOpencodeProviderBlockFromCatalog(port, catalog, hostname, config);
239
+ }
240
+
241
+ /** Back-compat helper for unit tests that assemble slugs/routed rows directly. */
242
+ export function buildOpencodeProviderBlock(
243
+ port: number,
244
+ nativeSlugs: readonly string[],
245
+ routedModels: readonly OpencodeRoutedModel[],
246
+ nativeContextWindow: (slug: string) => number | undefined = () => undefined,
247
+ hostname?: string,
248
+ config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
249
+ ): OpencodeProviderBlock {
250
+ return buildOpencodeProviderBlockFromCatalog(
251
+ port,
252
+ opencodeLaunchCatalog(nativeSlugs, routedModels, nativeContextWindow),
253
+ hostname,
254
+ config,
255
+ );
256
+ }
257
+
258
+ /**
259
+ * V2 counterpart of `buildOpencodeProviderBlock`. The launcher injects both generations,
260
+ * because only the V2 block carries selectable reasoning efforts.
261
+ */
262
+ export function buildOpencodeV2ProviderBlock(
263
+ port: number,
264
+ nativeSlugs: readonly string[],
265
+ routedModels: readonly OpencodeRoutedModel[],
266
+ nativeContextWindow: (slug: string) => number | undefined = () => undefined,
267
+ hostname?: string,
268
+ config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
269
+ ): OpencodeV2ProviderBlock {
270
+ return opencodeBlocks(
271
+ port,
272
+ opencodeLaunchCatalog(nativeSlugs, routedModels, nativeContextWindow),
273
+ hostname,
274
+ config,
275
+ ).v2;
276
+ }
277
+
278
+ /**
279
+ * Both generations from one catalog, in one pass. Every production path uses this: the two
280
+ * blocks are one document's fragments and have to agree on model set, names, connection, and
281
+ * variants, which building them together guarantees instead of merely expecting.
282
+ */
283
+ export function buildOpencodeProviderBlocksFromCatalog(
284
+ port: number,
285
+ catalogModels: readonly OpencodeCatalogModel[],
286
+ hostname?: string,
287
+ config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
288
+ ): OpencodeProviderBlocks {
289
+ return opencodeBlocks(port, catalogModels, hostname, config);
290
+ }
291
+
292
+ function opencodeBlocks(
293
+ port: number,
294
+ catalogModels: readonly OpencodeCatalogModel[],
295
+ hostname?: string,
296
+ config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
297
+ ): OpencodeProviderBlocks {
298
+ return opencodeProviderBlocks(opencodeProxyBaseUrl(port, hostname), catalogModels, config);
226
299
  }
227
300
 
228
301
  /** Default deadline for authenticated GET /api/models during `ocx opencode` launch. */
@@ -315,6 +388,12 @@ export function opencodeCatalogFromProxyRows(
315
388
  id: row.id,
316
389
  contextWindow: row.contextWindow,
317
390
  displayName: row.displayName,
391
+ ...(Array.isArray(row.reasoningEfforts) && row.reasoningEfforts.length > 0
392
+ ? { reasoningEfforts: [...row.reasoningEfforts] }
393
+ : {}),
394
+ ...(typeof row.defaultReasoningEffort === "string" && row.defaultReasoningEffort.length > 0
395
+ ? { defaultReasoningEffort: row.defaultReasoningEffort }
396
+ : {}),
318
397
  });
319
398
  }
320
399
  return catalog;
@@ -330,17 +409,19 @@ export function isOpencodeRuntimeConfigError(
330
409
  }
331
410
 
332
411
  /**
333
- * Merge inherited `OPENCODE_CONFIG_CONTENT` and override only `provider.opencodex`.
412
+ * Merge inherited `OPENCODE_CONFIG_CONTENT` and override only our own blocks:
413
+ * `provider.opencodex` (V1) and `providers.opencodex` (V2, the one carrying variants).
334
414
  * When no inline layer is present, emit the minimal runtime object for this launcher.
335
415
  */
336
416
  export function mergeOpencodeRuntimeConfig(
337
417
  inheritedContent: string | undefined,
338
- providerBlock: OpencodeProviderBlock,
418
+ blocks: OpencodeProviderBlocks,
339
419
  ): OpencodeGeneratedConfig | OpencodeRuntimeConfigError {
340
420
  if (!inheritedContent?.trim()) {
341
421
  return {
342
422
  $schema: OPENCODE_CONFIG_SCHEMA,
343
- provider: { [OPENCODE_PROVIDER_ID]: providerBlock },
423
+ provider: { [OPENCODE_PROVIDER_ID]: blocks.v1 },
424
+ providers: { [OPENCODE_PROVIDER_ID]: blocks.v2 },
344
425
  };
345
426
  }
346
427
  let parsed: unknown;
@@ -356,12 +437,20 @@ export function mergeOpencodeRuntimeConfig(
356
437
  if (existingProvider !== undefined && !isRecord(existingProvider)) {
357
438
  return { error: "OPENCODE_CONFIG_CONTENT provider must be a JSON object when present." };
358
439
  }
440
+ const existingProviders = parsed.providers;
441
+ if (existingProviders !== undefined && !isRecord(existingProviders)) {
442
+ return { error: "OPENCODE_CONFIG_CONTENT providers must be a JSON object when present." };
443
+ }
359
444
  return {
360
445
  ...parsed,
361
446
  $schema: typeof parsed.$schema === "string" ? parsed.$schema : OPENCODE_CONFIG_SCHEMA,
362
447
  provider: {
363
448
  ...(isRecord(existingProvider) ? existingProvider : {}),
364
- [OPENCODE_PROVIDER_ID]: providerBlock,
449
+ [OPENCODE_PROVIDER_ID]: blocks.v1,
450
+ },
451
+ providers: {
452
+ ...(isRecord(existingProviders) ? existingProviders : {}),
453
+ [OPENCODE_PROVIDER_ID]: blocks.v2,
365
454
  },
366
455
  } as OpencodeGeneratedConfig;
367
456
  }
@@ -375,10 +464,10 @@ export function buildOpencodeConfig(
375
464
  hostname?: string,
376
465
  config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
377
466
  ): OpencodeGeneratedConfig {
378
- const merged = mergeOpencodeRuntimeConfig(
379
- undefined,
380
- buildOpencodeProviderBlock(port, nativeSlugs, routedModels, nativeContextWindow, hostname, config),
381
- );
467
+ const merged = mergeOpencodeRuntimeConfig(undefined, {
468
+ v1: buildOpencodeProviderBlock(port, nativeSlugs, routedModels, nativeContextWindow, hostname, config),
469
+ v2: buildOpencodeV2ProviderBlock(port, nativeSlugs, routedModels, nativeContextWindow, hostname, config),
470
+ });
382
471
  if (isOpencodeRuntimeConfigError(merged)) {
383
472
  throw new Error(merged.error);
384
473
  }
@@ -400,11 +489,19 @@ function findGitRoot(start: string): string | null {
400
489
  }
401
490
  }
402
491
 
492
+ /**
493
+ * True when the file declares our provider in either generation. Both count: the launcher
494
+ * overwrites `provider.opencodex` and `providers.opencodex` alike, so a config that carries
495
+ * only the V2 block is overridden just as silently as one carrying only the V1 block.
496
+ */
403
497
  function configFileDefinesProvider(path: string): boolean {
404
498
  if (!existsSync(path)) return false;
405
499
  try {
406
500
  const parsed = parseJsonc(readFileSync(path, "utf8"));
407
- return isRecord(parsed) && isRecord(parsed.provider) && OPENCODE_PROVIDER_ID in parsed.provider;
501
+ if (!isRecord(parsed)) return false;
502
+ const legacy = isRecord(parsed.provider) && OPENCODE_PROVIDER_ID in parsed.provider;
503
+ const v2 = isRecord(parsed.providers) && OPENCODE_PROVIDER_ID in parsed.providers;
504
+ return legacy || v2;
408
505
  } catch {
409
506
  return false;
410
507
  }
@@ -460,16 +557,17 @@ export function opencodeProxyStartEnv(base: OpencodeLaunchEnv = process.env): Op
460
557
  }
461
558
 
462
559
  /**
463
- * Env assembly (unit-tested). Inherited inline config is merged and only
464
- * `provider.opencodex` is replaced; disk config layers stay untouched. The admission
465
- * key travels in the child env rather than in the inline config payload.
560
+ * Env assembly (unit-tested). Inherited inline config is merged and only our own blocks are
561
+ * replaced — `provider.opencodex` and `providers.opencodex`; disk config layers stay
562
+ * untouched. The admission key travels in the child env rather than in the inline config
563
+ * payload.
466
564
  */
467
565
  export function buildOpencodeEnv(
468
- providerBlock: OpencodeProviderBlock,
566
+ blocks: OpencodeProviderBlocks,
469
567
  apiKey: string,
470
568
  base: OpencodeLaunchEnv,
471
569
  ): OpencodeLaunchEnv | OpencodeRuntimeConfigError {
472
- const runtimeConfig = mergeOpencodeRuntimeConfig(base[OPENCODE_CONFIG_CONTENT_ENV], providerBlock);
570
+ const runtimeConfig = mergeOpencodeRuntimeConfig(base[OPENCODE_CONFIG_CONTENT_ENV], blocks);
473
571
  if (isOpencodeRuntimeConfigError(runtimeConfig)) return runtimeConfig;
474
572
  return {
475
573
  ...base,
@@ -547,22 +645,17 @@ export async function cmdOpencode(args: string[]): Promise<number> {
547
645
  return 1;
548
646
  }
549
647
  const catalog = opencodeCatalogFromProxyRows(proxyModels, config);
550
- const providerBlock = buildOpencodeProviderBlockFromCatalog(
551
- live.port,
552
- catalog,
553
- live.hostname,
554
- config,
555
- );
556
- const baseUrl = providerBlock.options.baseURL;
648
+ const blocks = buildOpencodeProviderBlocksFromCatalog(live.port, catalog, live.hostname, config);
649
+ const baseUrl = blocks.v1.options.baseURL;
557
650
  const modelCount = catalog.length;
558
651
  console.error(`✅ opencode wired to ${baseUrl} — ${modelCount} model(s) under provider \`${OPENCODE_PROVIDER_ID}\`.`);
559
- console.error(" Your existing opencode config files are left untouched; only the runtime provider block is injected.");
652
+ console.error(" Your existing opencode config files are left untouched; only the runtime provider blocks are injected.");
560
653
  const providerOverride = opencodeProviderOverridePath(process.cwd());
561
654
  if (providerOverride) {
562
- console.error(`ℹ ${providerOverride} also defines provider.${OPENCODE_PROVIDER_ID}; the runtime layer from ocx opencode overrides it for this launch.`);
655
+ console.error(`ℹ ${providerOverride} also defines our provider key; the runtime layer from ocx opencode overrides it for this launch.`);
563
656
  }
564
657
 
565
- const builtEnv = buildOpencodeEnv(providerBlock, apiKey, process.env);
658
+ const builtEnv = buildOpencodeEnv(blocks, apiKey, process.env);
566
659
  if ("error" in builtEnv) {
567
660
  console.error(`❌ ${builtEnv.error}`);
568
661
  return 1;
@@ -65,7 +65,7 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
65
65
  summary: "Run as a background service.",
66
66
  details: [
67
67
  "With no subcommand, installs when absent or repairs/restarts an existing service.",
68
- "`restart` is an alias of `repair` and does not re-register an installed service.",
68
+ "`restart` aliases `repair`; healthy Windows tasks are reused, while stale definitions may re-register and elevate.",
69
69
  "Use `ocx service status` to see diagnostics and log paths.",
70
70
  ],
71
71
  },
@@ -244,8 +244,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
244
244
  { name: "api-key", usage: "ocx api-key <list|create|remove> ...", summary: "Alias of ocx access key." },
245
245
  {
246
246
  name: "export",
247
- usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime> [--json] [--out <path>] [--force]",
248
- summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent) wired to the running proxy.",
247
+ usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime|aside> [--json] [--out <path>] [--force]",
248
+ summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside) wired to the running proxy.",
249
249
  details: [
250
250
  "--json prints the generated document as JSON on stdout; use --out for the client's native format.",
251
251
  "--out <path> writes the native config there and refuses to replace an existing file without --force.",
@@ -268,8 +268,13 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
268
268
  },
269
269
  {
270
270
  name: "system",
271
- usage: "ocx system <status|settings|startup|diagnostics|sync|update> ...",
272
- summary: "Manage headless runtime settings, startup, sync, diagnostics, and updates.",
271
+ usage: "ocx system <status|settings|startup|diagnostics|sync|codex-app-server|codex-restart|update|codex-cli-update> ...",
272
+ summary: "Manage headless runtime settings, startup, sync, diagnostics, OpenCodex updates, and read-only Codex CLI inspection.",
273
+ details: [
274
+ "system update manages OpenCodex itself.",
275
+ "ocx system codex-cli-update check [--json]",
276
+ "The Codex CLI inspection command makes no package-registry request, does not execute Codex or npm, install or repair software, control a process, or write configuration or cache state.",
277
+ ],
273
278
  },
274
279
  {
275
280
  name: "config",
@@ -309,11 +314,12 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
309
314
  summary: "Launch opencode wired to the proxy (runtime provider config).",
310
315
  details: [
311
316
  "Ensures the proxy is running, then execs `opencode` with the generated `provider.opencodex`",
312
- "block injected through OpenCode's inline runtime layer (`OPENCODE_CONFIG_CONTENT`). Any",
313
- "existing inline config in the environment is preserved and only `provider.opencodex` is",
314
- "overwritten for this launch.",
315
- "Global/project opencode.json may be read to warn about an existing provider.opencodex",
316
- "override; on-disk files are never modified.",
317
+ "and `providers.opencodex` blocks injected through OpenCode's inline runtime layer",
318
+ "(`OPENCODE_CONFIG_CONTENT`). Any existing inline config in the environment is preserved",
319
+ "and only `provider.opencodex` and `providers.opencodex` are overwritten for this launch.",
320
+ "Only the V2 block (`providers.opencodex`) carries the reasoning-effort variants.",
321
+ "Global/project opencode.json may be read to warn about an existing provider.opencodex or",
322
+ "providers.opencodex override; on-disk files are never modified.",
317
323
  "Routed models appear in the model picker as opencodex/<provider>/<model>.",
318
324
  "Stop using `ocx opencode` and plain `opencode` behaves exactly as before.",
319
325
  ],
@@ -19,6 +19,7 @@ const USAGE = `Usage:
19
19
  ocx system sync [--json]
20
20
  ocx system codex-app-server [--json]
21
21
  ocx system codex-restart --yes [--json]
22
+ ocx system codex-cli-update check [--json]
22
23
  ocx system update check [--channel <latest|preview>] [--json]
23
24
  ocx system update run [--channel <latest|preview>] [--restart <on|off>] --yes [--json]
24
25
  ocx system update status <job-id> [--json]`;
@@ -95,8 +96,12 @@ async function update(argv: string[], deps: RuntimeApiDeps): Promise<void> {
95
96
  }
96
97
 
97
98
  export async function handleSystemCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
99
+ const [sub = "status", ...rest] = argv;
100
+ if (sub === "codex-cli-update") {
101
+ const { handleCodexCliUpdateCommand } = await import("./codex-cli-update");
102
+ return await handleCodexCliUpdateCommand(rest);
103
+ }
98
104
  return runCliAction(async () => {
99
- const [sub = "status", ...rest] = argv;
100
105
  if (sub === "status") await status(rest, deps);
101
106
  else if (sub === "settings") await settings(rest, deps);
102
107
  else if (sub === "startup") await startup(rest, deps);