sneakoscope 4.7.0 → 4.7.3

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.
@@ -6,6 +6,10 @@ import { EMPTY_CODEX_INFO, getCodexInfo } from './codex-adapter.js';
6
6
  import { CODEX_CHROME_EXTENSION_DOC_URL, DEFAULT_CODEX_APP_PLUGINS as DEFAULT_CODEX_APP_PLUGIN_TUPLES, RESERVED_CODEX_PLUGIN_SKILL_NAMES } from './routes.js';
7
7
  import { PRODUCT_DESIGN_PLUGIN, normalizeProductDesignPluginEvidence } from './product-design-plugin.js';
8
8
  import { PRODUCT_DESIGN_AUTO_INSTALL_ENV, ensureProductDesignPluginInstalled, productDesignAutoInstallRequested } from './product-design-app-server.js';
9
+ import { GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from './codex-app/glm-model-profile.js';
10
+ import { GLM_52_OPENROUTER_MODEL } from './providers/glm/glm-52-settings.js';
11
+ import { resolveOpenRouterApiKey } from './providers/openrouter/openrouter-secret-store.js';
12
+ import { codexLbEnvPath, parseShellEnvValue } from './codex-lb/codex-lb-env.js';
9
13
  export const CODEX_APP_DOCS_URL = 'https://developers.openai.com/codex/app/features';
10
14
  export const CODEX_CHANGELOG_URL = 'https://developers.openai.com/codex/changelog';
11
15
  export const CODEX_ACCESS_TOKENS_DOCS_URL = 'https://developers.openai.com/codex/enterprise/access-tokens';
@@ -133,6 +137,8 @@ export async function codexAppIntegrationStatus(opts = {}) {
133
137
  const productDesignPlugin = await codexProductDesignPluginStatus(opts);
134
138
  const pluginSkillShadows = await codexPluginSkillShadowStatus(opts);
135
139
  const fastModeConfig = await codexFastModeConfigStatus(opts);
140
+ const providerModelUi = await codexProviderModelUiStatus(opts);
141
+ const nativeSksMenu = codexNativeSksMenuStatus();
136
142
  const computerUseMcpListed = /computer[-_ ]?use/i.test(mcpText);
137
143
  const browserUseMcpListed = /browser[-_ ]?use/i.test(mcpText);
138
144
  const imageGenerationReady = codexFeatureEnabled(featureText, 'image_generation');
@@ -161,7 +167,8 @@ export async function codexAppIntegrationStatus(opts = {}) {
161
167
  app: {
162
168
  installed: appInstalled,
163
169
  path: appPath,
164
- docs_url: CODEX_APP_DOCS_URL
170
+ docs_url: CODEX_APP_DOCS_URL,
171
+ sks_menu: nativeSksMenu
165
172
  },
166
173
  codex_cli: {
167
174
  ok: Boolean(codex.bin),
@@ -186,6 +193,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
186
193
  required_flags: requiredFeatureFlags,
187
194
  required_flags_ok: requiredFeatureFlagsOk,
188
195
  fast_mode_config: fastModeConfig,
196
+ provider_model_ui: providerModelUi,
189
197
  git_actions: gitActions,
190
198
  image_generation: imageGenerationReady,
191
199
  image_generation_source: imageGenerationReady ? 'codex_features_list' : 'missing',
@@ -223,7 +231,7 @@ export async function codexAppIntegrationStatus(opts = {}) {
223
231
  }
224
232
  },
225
233
  chrome_extension: chromeExtension,
226
- guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, defaultPlugins, productDesignPlugin, pluginSkillShadows, fastModeConfig, gitActions, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl })
234
+ guidance: codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags, requiredFeatureFlagsOk, defaultPlugins, productDesignPlugin, pluginSkillShadows, fastModeConfig, providerModelUi, nativeSksMenu, gitActions, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl })
227
235
  };
228
236
  }
229
237
  export async function codexChromeExtensionStatus(opts = {}) {
@@ -442,7 +450,25 @@ export function codexAccessTokenStatus(env = process.env) {
442
450
  : ['No CODEX_ACCESS_TOKEN detected in the current process environment. This is fine for interactive ChatGPT login or API-key auth.']
443
451
  };
444
452
  }
445
- export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, defaultPlugins = { ok: true, missing_enabled: [] }, productDesignPlugin = null, pluginSkillShadows = { ok: true, blocking: [] }, fastModeConfig = { ok: true, blockers: [] }, gitActions = { ok: true, blockers: [] }, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl }) {
453
+ function codexNativeSksMenuStatus() {
454
+ return {
455
+ schema: 'sks.codex-app-native-menu.v1',
456
+ supported: false,
457
+ status: 'unsupported_official_api',
458
+ requested_label: 'SKS',
459
+ requested_position: 'between Window and Help',
460
+ alternatives: [
461
+ 'sks codex-lb use-codex-lb',
462
+ 'sks codex-lb use-oauth',
463
+ 'sks codex-app set-openrouter-key --api-key-stdin',
464
+ 'codex://settings'
465
+ ],
466
+ evidence: [
467
+ 'Codex official docs expose settings deep links, slash commands, skills/plugins, and app-server/remote-control surfaces, not third-party native macOS menu insertion.'
468
+ ]
469
+ };
470
+ }
471
+ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, requiredFeatureFlags = {}, requiredFeatureFlagsOk = true, defaultPlugins = { ok: true, missing_enabled: [] }, productDesignPlugin = null, pluginSkillShadows = { ok: true, blocking: [] }, fastModeConfig = { ok: true, blockers: [] }, providerModelUi = null, nativeSksMenu = null, gitActions = { ok: true, blockers: [] }, imageGenerationReady, inAppBrowserReady, browserUseFeatureReady, computerUseReady, browserUseReady, browserToolReady, computerUseMcpListed, browserUseMcpListed, chromeExtension, remoteControl }) {
446
472
  const lines = [];
447
473
  if (!appInstalled) {
448
474
  lines.push('Install and open Codex App for first-party MCP/plugin tools. SKS Zellij launch can still run with Codex CLI alone, but Codex Computer Use and imagegen/gpt-image-2 evidence will be unavailable until Codex App is ready.');
@@ -503,6 +529,28 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
503
529
  lines.push(`Codex App speed selector can be hidden or locked by config: ${fastModeConfig.blockers.join(', ')}.`);
504
530
  lines.push('Run: sks doctor --fix');
505
531
  }
532
+ if (providerModelUi?.glm?.exposed && providerModelUi.glm.openrouter_key_present) {
533
+ lines.push(`GLM model selector profile is exposed: ${providerModelUi.glm.model} via profiles ${providerModelUi.glm.profiles_present.join(', ')}.`);
534
+ }
535
+ else if (providerModelUi?.glm?.exposed) {
536
+ lines.push(`GLM model selector profile is exposed for ${providerModelUi.glm.model}, but the OpenRouter key is missing.`);
537
+ lines.push(`Run: ${providerModelUi.glm.key_command}; restart Codex App if the model picker was already open.`);
538
+ }
539
+ else if (providerModelUi?.glm) {
540
+ lines.push(`GLM model selector profile is not exposed yet: ${providerModelUi.glm.blockers.join(', ') || 'profile setup required'}.`);
541
+ lines.push(`Run: ${providerModelUi.glm.key_command}; then ${providerModelUi.glm.install_command}; restart Codex App if the model picker was already open.`);
542
+ }
543
+ if (providerModelUi?.codex_lb?.key_entry_visible && providerModelUi.codex_lb.key_present && providerModelUi.codex_lb.provider_present && providerModelUi.codex_lb.provider_contract_ok && providerModelUi.codex_lb.base_url_present) {
544
+ lines.push(`codex-lb key entry path is visible and configured; update command ${providerModelUi.codex_lb.set_key_command}.`);
545
+ }
546
+ else if (providerModelUi?.codex_lb?.key_entry_visible) {
547
+ lines.push(`codex-lb key entry path is visible, but setup is incomplete: ${providerModelUi.codex_lb.blockers.join(', ') || 'setup required'}.`);
548
+ lines.push(`Run: ${providerModelUi.codex_lb.setup_command} or ${providerModelUi.codex_lb.set_key_command}.`);
549
+ }
550
+ else if (providerModelUi?.codex_lb) {
551
+ lines.push(`codex-lb key entry path is not exposed yet: ${providerModelUi.codex_lb.blockers.join(', ') || 'setup required'}.`);
552
+ lines.push(`Run: ${providerModelUi.codex_lb.setup_command} or ${providerModelUi.codex_lb.set_key_command}.`);
553
+ }
506
554
  if (!gitActions?.ok) {
507
555
  lines.push(`Codex App git commit/push actions are blocked: ${gitActions?.blockers?.join(', ') || 'git action readiness'}. The app Commit, Push, Commit and Push, and PR flows need codex_git_commit, hooks, and Codex CLI remote-control support.`);
508
556
  lines.push(`Run: sks doctor --fix; if remote-control is still blocked, update Codex CLI to ${CODEX_REMOTE_CONTROL_MIN_VERSION}+ and restart older app-server/TUI sessions.`);
@@ -510,6 +558,9 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
510
558
  else {
511
559
  lines.push('Codex App git actions are enabled for Commit, Push, Commit and Push, and PR flows; SKS hooks treat those app metadata actions as lightweight git UI actions.');
512
560
  }
561
+ if (nativeSksMenu?.supported === false) {
562
+ lines.push('Native macOS SKS menu is not installed because Codex App does not expose a documented third-party native menu extension API; use `sks codex-lb use-codex-lb`, `sks codex-lb use-oauth`, and `sks codex-app set-openrouter-key --api-key-stdin` instead.');
563
+ }
513
564
  if (appInstalled && (!computerUseReady || !browserToolReady)) {
514
565
  lines.push('Open Codex App settings and enable recommended MCP/plugin tools. Codex CLI 0.130.0+ remote-control/app-server sessions can pick up config changes live; restart older CLI/TUI sessions.');
515
566
  lines.push(`Required for SKS web QA/UX/browser evidence: Codex Chrome Extension first (${CODEX_CHROME_EXTENSION_SETUP_DOCS_URL}). Computer Use is reserved for native Mac/non-web surfaces.`);
@@ -551,6 +602,10 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
551
602
  `Remote Ctrl: ${status.remote_control?.ok ? 'ok' : 'missing'}${status.remote_control?.codex_cli?.version_number ? ` min ${status.remote_control.min_version}` : ''}`,
552
603
  `App Flags: ${status.features?.required_flags_ok ? 'ok' : `missing ${missingRequiredFeatureFlags(status.features?.required_flags).join(', ') || 'required flags'}`}`,
553
604
  `Fast UI: ${status.features?.fast_mode_config?.ok ? 'ok' : `locked ${(status.features?.fast_mode_config?.blockers || []).join(', ') || 'config'}`}`,
605
+ `SKS Menu: ${sksNativeMenuSummary(status.app?.sks_menu)}`,
606
+ `Provider UI:${providerModelUiSummary(status.features?.provider_model_ui)}`,
607
+ `GLM Model: ${glmModelUiSummary(status.features?.provider_model_ui?.glm)}`,
608
+ `codex-lb Key:${codexLbKeyUiSummary(status.features?.provider_model_ui?.codex_lb)}`,
554
609
  `Default Plugins:${status.plugins?.default_plugins?.ok ? ' ok' : ` missing ${defaultPluginMissingSummary(status.plugins?.default_plugins) || 'plugin install/config'}`}`,
555
610
  `Product Design:${productDesignStatusSummary(status.plugins?.design_product)}`,
556
611
  `Plugin Picker:${status.plugins?.picker?.ok ? ' ok' : ` blocked ${pluginPickerBlockers(status).join(', ') || 'config'}`}`,
@@ -798,6 +853,8 @@ async function codexFastModeConfigStatus(opts = {}) {
798
853
  if (!config.text)
799
854
  continue;
800
855
  const topLevel = topLevelToml(config.text);
856
+ if (/(^|\n)\s*model\s*=/.test(topLevel))
857
+ blockers.push(`${config.scope}:top_level_model`);
801
858
  if (/(^|\n)\s*model_reasoning_effort\s*=/.test(topLevel))
802
859
  blockers.push(`${config.scope}:top_level_model_reasoning_effort`);
803
860
  if (/(^|\n)\s*fast_default_opt_out\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(tomlTable(config.text, 'notice')))
@@ -815,6 +872,134 @@ async function codexFastModeConfigStatus(opts = {}) {
815
872
  blockers
816
873
  };
817
874
  }
875
+ export async function codexProviderModelUiStatus(opts = {}) {
876
+ const inputEnv = opts.env || process.env;
877
+ const home = opts.home || inputEnv.HOME || os.homedir();
878
+ const cwd = opts.cwd || process.cwd();
879
+ const env = { ...inputEnv, HOME: String(opts.home || inputEnv.HOME || home) };
880
+ const globalConfigPath = opts.configPath || path.join(home || '', '.codex', 'config.toml');
881
+ const projectConfigPath = path.join(cwd || '', '.codex', 'config.toml');
882
+ const globalConfig = await readTextIfExists(globalConfigPath);
883
+ const projectConfig = path.resolve(projectConfigPath) === path.resolve(globalConfigPath)
884
+ ? ''
885
+ : await readTextIfExists(projectConfigPath);
886
+ const configText = `${globalConfig}\n${projectConfig}`;
887
+ const openRouterKey = await resolveOpenRouterApiKey({ env }).catch((err) => ({
888
+ key: null,
889
+ source: null,
890
+ key_preview: null,
891
+ blockers: ['openrouter_key_lookup_failed'],
892
+ warnings: [err?.message || 'openrouter key lookup failed']
893
+ }));
894
+ const codexLbEnvFilePath = opts.codexLbEnvPath || codexLbEnvPath(home);
895
+ const codexLbEnvText = await readTextIfExists(codexLbEnvFilePath);
896
+ const codexLbApiKeySource = String(env.CODEX_LB_API_KEY || '').trim()
897
+ ? 'process.env'
898
+ : parseShellEnvValue(codexLbEnvText, 'CODEX_LB_API_KEY')
899
+ ? 'env-file'
900
+ : 'missing';
901
+ const codexLbBaseUrlSource = String(env.CODEX_LB_BASE_URL || '').trim()
902
+ ? 'process.env'
903
+ : parseShellEnvValue(codexLbEnvText, 'CODEX_LB_BASE_URL')
904
+ ? 'env-file'
905
+ : 'missing';
906
+ const openrouterProvider = tomlTableAny(configText, [
907
+ `model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}`,
908
+ `model_providers."${GLM_CODEX_CONFIG_PROVIDER_ID}"`
909
+ ]);
910
+ const glmProviderPresent = Boolean(openrouterProvider);
911
+ const profilesPresent = [];
912
+ const profilesMissing = [];
913
+ const glmProfileBlockers = [];
914
+ for (const profile of GLM_CODEX_CONFIG_REASONING_PROFILES) {
915
+ const body = tomlTableAny(configText, [`profiles.${profile.id}`, `profiles."${profile.id}"`]);
916
+ if (!body) {
917
+ profilesMissing.push(profile.id);
918
+ glmProfileBlockers.push(`glm_profile_missing:${profile.id}`);
919
+ continue;
920
+ }
921
+ profilesPresent.push(profile.id);
922
+ if (!hasTomlString(body, 'model_provider', GLM_CODEX_CONFIG_PROVIDER_ID))
923
+ glmProfileBlockers.push(`glm_profile_provider_invalid:${profile.id}`);
924
+ if (!hasTomlString(body, 'model', GLM_52_OPENROUTER_MODEL))
925
+ glmProfileBlockers.push(`glm_profile_model_invalid:${profile.id}`);
926
+ if (!hasTomlString(body, 'model_reasoning_effort', profile.reasoning_effort))
927
+ glmProfileBlockers.push(`glm_profile_reasoning_invalid:${profile.id}`);
928
+ }
929
+ const codexLbProvider = tomlTableAny(configText, ['model_providers.codex-lb', 'model_providers."codex-lb"']);
930
+ const codexLbProviderPresent = Boolean(codexLbProvider);
931
+ const codexLbProviderContractOk = codexLbProviderPresent
932
+ && hasTomlString(codexLbProvider, 'name', 'openai')
933
+ && hasTomlString(codexLbProvider, 'wire_api', 'responses')
934
+ && hasTomlString(codexLbProvider, 'env_key', 'CODEX_LB_API_KEY')
935
+ && hasTomlBoolean(codexLbProvider, 'supports_websockets', true)
936
+ && hasTomlBoolean(codexLbProvider, 'requires_openai_auth', true);
937
+ const codexLbSelectedDefault = /(?:^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevelToml(globalConfig));
938
+ const glmBlockers = [
939
+ ...(glmProviderPresent ? [] : ['glm_openrouter_provider_missing']),
940
+ ...glmProfileBlockers,
941
+ ...(openRouterKey.key ? [] : ['openrouter_key_missing'])
942
+ ];
943
+ const codexLbBlockers = [
944
+ ...(codexLbProviderPresent ? [] : ['codex_lb_provider_missing']),
945
+ ...(codexLbProviderPresent && !codexLbProviderContractOk ? ['codex_lb_provider_contract_drift'] : []),
946
+ ...(codexLbApiKeySource !== 'missing' ? [] : ['codex_lb_api_key_missing']),
947
+ ...(codexLbBaseUrlSource !== 'missing' ? [] : ['codex_lb_base_url_missing'])
948
+ ];
949
+ const blockers = [...glmBlockers, ...codexLbBlockers];
950
+ const keyCommand = 'sks codex-app set-openrouter-key --api-key-stdin';
951
+ const installCommand = 'sks codex-app glm-profile install';
952
+ const setupCommand = 'sks codex-lb setup --host <domain> --api-key-stdin --yes';
953
+ const setKeyCommand = 'sks codex-lb set-key --api-key-stdin';
954
+ return {
955
+ schema: 'sks.codex-app-provider-model-ui.v1',
956
+ ok: blockers.length === 0,
957
+ checked: true,
958
+ status: blockers.length === 0 ? 'ready' : 'setup_required',
959
+ config_paths: {
960
+ codex_config: globalConfigPath,
961
+ project_config: projectConfig ? projectConfigPath : null,
962
+ codex_lb_env: codexLbEnvFilePath
963
+ },
964
+ glm: {
965
+ exposed: glmProviderPresent && profilesMissing.length === 0 && glmProfileBlockers.length === 0,
966
+ provider_present: glmProviderPresent,
967
+ provider: GLM_CODEX_CONFIG_PROVIDER_ID,
968
+ model: GLM_52_OPENROUTER_MODEL,
969
+ profiles_present: profilesPresent,
970
+ profiles_missing: profilesMissing,
971
+ openrouter_key_present: Boolean(openRouterKey.key),
972
+ openrouter_key_source: openRouterKey.source || null,
973
+ openrouter_key_preview: openRouterKey.key_preview || null,
974
+ key_command: keyCommand,
975
+ install_command: installCommand,
976
+ doctor_command: 'sks codex-app glm-profile doctor',
977
+ blockers: glmBlockers
978
+ },
979
+ codex_lb: {
980
+ key_entry_visible: true,
981
+ provider_present: codexLbProviderPresent,
982
+ provider_contract_ok: codexLbProviderContractOk,
983
+ selected_default: codexLbSelectedDefault,
984
+ key_present: codexLbApiKeySource !== 'missing',
985
+ api_key_source: codexLbApiKeySource,
986
+ base_url_present: codexLbBaseUrlSource !== 'missing',
987
+ base_url_source: codexLbBaseUrlSource,
988
+ setup_command: setupCommand,
989
+ set_key_command: setKeyCommand,
990
+ status_command: 'sks codex-lb status',
991
+ blockers: codexLbBlockers
992
+ },
993
+ ui_actions: [
994
+ keyCommand,
995
+ installCommand,
996
+ setupCommand,
997
+ setKeyCommand,
998
+ 'sks codex-app status'
999
+ ],
1000
+ blockers
1001
+ };
1002
+ }
818
1003
  async function readTextIfExists(file) {
819
1004
  try {
820
1005
  return await fsp.readFile(file, 'utf8');
@@ -874,6 +1059,38 @@ function productDesignStatusSummary(status = {}) {
874
1059
  return ' app-server unverified';
875
1060
  return ' not checked';
876
1061
  }
1062
+ function providerModelUiSummary(status = {}) {
1063
+ if (!status?.checked)
1064
+ return ' not checked';
1065
+ if (status.ok)
1066
+ return ' ok (GLM + codex-lb)';
1067
+ return ` setup ${(status.blockers || []).join(', ') || 'required'}`;
1068
+ }
1069
+ function sksNativeMenuSummary(status = {}) {
1070
+ if (!status)
1071
+ return 'not checked';
1072
+ if (status.supported)
1073
+ return 'ok';
1074
+ return `${status.status || 'unsupported'} (use CLI/settings)`;
1075
+ }
1076
+ function glmModelUiSummary(status = {}) {
1077
+ if (!status)
1078
+ return 'not checked';
1079
+ if (status.exposed) {
1080
+ const key = status.openrouter_key_present ? `key ${status.openrouter_key_source || 'present'}` : 'key missing';
1081
+ return `ok ${status.model} (${status.profiles_present?.length || 0} profiles, ${key})`;
1082
+ }
1083
+ return `setup ${(status.blockers || []).join(', ') || 'profile missing'}; run ${status.key_command || 'sks codex-app set-openrouter-key --api-key-stdin'}`;
1084
+ }
1085
+ function codexLbKeyUiSummary(status = {}) {
1086
+ if (!status)
1087
+ return ' not checked';
1088
+ if (status.key_present && status.provider_contract_ok !== false)
1089
+ return ` configured (${status.api_key_source || 'present'})`;
1090
+ if (status.key_present)
1091
+ return ` setup provider drift; run ${status.setup_command || 'sks codex-lb setup --host <domain> --api-key-stdin --yes'}`;
1092
+ return ` missing (input: ${status.setup_command || 'sks codex-lb setup --host <domain> --api-key-stdin --yes'})`;
1093
+ }
877
1094
  function defaultPluginMissingSummary(defaultPlugins = {}) {
878
1095
  return [
879
1096
  ...(defaultPlugins?.missing_installed || []),
@@ -889,6 +1106,23 @@ function tomlTable(text = '', table = '') {
889
1106
  const re = new RegExp(`(?:^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`);
890
1107
  return String(text || '').match(re)?.[1] || '';
891
1108
  }
1109
+ function tomlTableAny(text = '', tables = []) {
1110
+ for (const table of tables) {
1111
+ const body = tomlTable(text, table);
1112
+ if (body)
1113
+ return body;
1114
+ }
1115
+ return '';
1116
+ }
1117
+ function hasTomlString(text = '', key = '', value = '') {
1118
+ const re = new RegExp(`(?:^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?(?=\\n|$)`);
1119
+ return re.test(String(text || ''));
1120
+ }
1121
+ function hasTomlBoolean(text = '', key = '', value) {
1122
+ const expected = value ? 'true' : 'false';
1123
+ const re = new RegExp(`(?:^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*${expected}\\s*(?:#.*)?(?=\\n|$)`);
1124
+ return re.test(String(text || ''));
1125
+ }
892
1126
  function escapeRegExp(text = '') {
893
1127
  return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
894
1128
  }
@@ -19,12 +19,12 @@ export function buildCodexSdkConfig(input) {
19
19
  config.model_provider = 'codex-lb';
20
20
  config.model_providers = {
21
21
  'codex-lb': {
22
- name: 'OpenAI',
22
+ name: 'openai',
23
23
  base_url: codexLbBaseUrl,
24
24
  wire_api: 'responses',
25
25
  env_key: 'CODEX_LB_API_KEY',
26
26
  supports_websockets: true,
27
- requires_openai_auth: false
27
+ requires_openai_auth: true
28
28
  }
29
29
  };
30
30
  }
@@ -464,12 +464,12 @@ async function ensurePythonCodexLbConfig(env, config) {
464
464
  'approval_policy = "never"',
465
465
  '',
466
466
  '[model_providers.codex-lb]',
467
- 'name = "OpenAI"',
467
+ 'name = "openai"',
468
468
  `base_url = ${tomlQuote(lbBaseUrl)}`,
469
469
  'wire_api = "responses"',
470
470
  'env_key = "CODEX_LB_API_KEY"',
471
471
  'supports_websockets = true',
472
- 'requires_openai_auth = false',
472
+ 'requires_openai_auth = true',
473
473
  ''
474
474
  ].join('\n');
475
475
  await writeTextAtomic(path.join(codexHome, 'config.toml'), text);
@@ -14,7 +14,7 @@ export function buildCodexLbSetupPlan(answers, opts = {}) {
14
14
  if (answers.install_shell_profile !== 'skip' && !answers.write_env_file)
15
15
  blockers.push('shell_profile_snippet_requires_env_file');
16
16
  const actions = [
17
- { type: 'write_config_provider', target: configPath, effect: 'write or update [model_providers.codex-lb] with the normalized base URL, CODEX_LB_API_KEY env_key, and requires_openai_auth=false' }
17
+ { type: 'write_config_provider', target: configPath, effect: 'write or update [model_providers.codex-lb] with name="openai", the normalized base URL, CODEX_LB_API_KEY env_key, WebSocket support, and requires_openai_auth=true for Codex App routing' }
18
18
  ];
19
19
  if (answers.use_as_default_provider) {
20
20
  actions.push({ type: 'select_default_provider', target: configPath, effect: 'set top-level model_provider = "codex-lb"' });
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '4.7.0';
8
+ export const PACKAGE_VERSION = '4.7.3';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -137,8 +137,8 @@ function codexLbAuthBlocker(state) {
137
137
  return 'codex_lb_not_selected';
138
138
  if (!state.providerConfigured)
139
139
  return 'codex_lb_provider_missing';
140
- if (state.requiresOpenAiAuth !== false) {
141
- return state.requiresOpenAiAuth === true ? 'codex_lb_requires_openai_auth' : 'codex_lb_requires_openai_auth_not_disabled';
140
+ if (state.requiresOpenAiAuth !== true) {
141
+ return state.requiresOpenAiAuth === false ? 'codex_lb_legacy_openai_auth_disabled' : 'codex_lb_requires_openai_auth_missing';
142
142
  }
143
143
  if (state.envKey !== 'CODEX_LB_API_KEY')
144
144
  return 'codex_lb_env_key_missing_or_unsupported';
package/dist/core/init.js CHANGED
@@ -69,8 +69,7 @@ export function hasTopLevelCodexModeLock(text = '') {
69
69
  const lines = String(text || '').split('\n');
70
70
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
71
71
  const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
72
- const model = top.match(/^model\s*=\s*"([^"]+)"/m)?.[1];
73
- return (Boolean(model) && model !== 'gpt-5.5') || /^model_reasoning_effort\s*=/m.test(top);
72
+ return /^model\s*=/.test(top) || /^model_reasoning_effort\s*=/m.test(top);
74
73
  }
75
74
  export function hasDeprecatedCodexHooksFeatureFlag(text = '') {
76
75
  const lines = String(text || '').split('\n');
@@ -590,10 +589,8 @@ export async function initProject(root, opts = {}) {
590
589
  let next = removeLegacyTopLevelCodexModeLocks(String(existingContent || '').trimEnd());
591
590
  next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
592
591
  next = removeTomlTableKey(next, 'features', 'codex_hooks');
593
- // User-overridable top-level keys: seed defaults only when ABSENT so an
594
- // upgrade never clobbers a user's chosen model/service_tier. model_reasoning_effort
595
- // is intentionally left untouched at the top level.
596
- next = upsertTopLevelTomlStringIfAbsent(next, 'model', 'gpt-5.5');
592
+ // Model/reasoning defaults live in profiles. Leaving them out of top-level config
593
+ // keeps Codex Desktop's native model and speed selectors available.
597
594
  next = upsertTopLevelTomlBooleanIfAbsent(next, 'suppress_unstable_features_warning', true);
598
595
  // Codex App feature flags: SET-IF-ABSENT only (see note above).
599
596
  for (const flag of MANAGED_CODEX_FEATURE_FLAGS) {
@@ -646,7 +643,7 @@ export async function initProject(root, opts = {}) {
646
643
  next = shouldSelectCodexLb
647
644
  ? upsertTopLevelTomlString(next, 'model_provider', 'codex-lb')
648
645
  : removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
649
- next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = false`);
646
+ next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "openai"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = true`);
650
647
  return `${next.trim()}\n`;
651
648
  }
652
649
  function parseCodexLbEnvKey(text = '') {
@@ -704,7 +701,7 @@ export async function initProject(root, opts = {}) {
704
701
  return lines.filter((line, index) => {
705
702
  if (index >= end)
706
703
  return true;
707
- return !/^\s*model_reasoning_effort\s*=/.test(line);
704
+ return !/^\s*(?:model|model_reasoning_effort)\s*=/.test(line);
708
705
  }).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
709
706
  }
710
707
  function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
@@ -1,6 +1,6 @@
1
1
  import { REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
2
2
  export const MANAGED_ASSET_SCHEMA_VERSION = 1;
3
- export const MANAGED_ASSET_VERSION = '4.7.0';
3
+ export const MANAGED_ASSET_VERSION = '4.7.3';
4
4
  export const MANAGED_ASSET_MARKER = 'SKS-MANAGED-ASSET';
5
5
  export const MANAGED_AGENT_ROLES = Object.freeze([
6
6
  role('sks-explorer', 'analysis-scout.toml', 'analysis_scout', 'SKS analysis scout for bounded read/write slices retained for stale Codex agent-role config repair.', 'workspace-write', ['analysis-scout', 'analysis_scout']),
@@ -10,7 +10,7 @@ export async function resolveProviderContext(input = {}) {
10
10
  const codexLbProviderBlockPresent = hasCodexLbProviderBlock(configText);
11
11
  const codexLbEnvKey = codexLbProviderEnvKey(configText) || (codexLbProviderBlockPresent ? 'CODEX_LB_API_KEY' : null);
12
12
  const codexLbRequiresOpenAiAuth = codexLbProviderRequiresOpenAiAuth(configText);
13
- const codexLbProviderValid = codexLbProviderBlockPresent && (codexLbRequiresOpenAiAuth === false || codexLbRequiresOpenAiAuth == null);
13
+ const codexLbProviderValid = codexLbProviderBlockPresent && codexLbRequiresOpenAiAuth === true;
14
14
  const openaiKey = Boolean(String(env.OPENAI_API_KEY || '').trim());
15
15
  const lbKey = Boolean(String((codexLbEnvKey ? env[codexLbEnvKey] : env.CODEX_LB_API_KEY) || env.CODEX_LB_API_KEY || '').trim());
16
16
  const envProvider = String(env.SKS_MODEL_PROVIDER || env.CODEX_MODEL_PROVIDER || env.OPENAI_MODEL_PROVIDER || '').trim();
@@ -702,7 +702,7 @@ export const COMMAND_CATALOG = [
702
702
  { name: 'update', usage: 'sks update check|now [--version <version>] [--json] [--dry-run]', description: 'Check for SKS updates or install the requested package version through npm global mode.' },
703
703
  { name: 'deps', usage: 'sks deps check [--json] [--yes]', description: 'Check Node/npm, Codex CLI, and Zellij readiness; pass --yes to repair missing Codex CLI/Zellij tooling when supported.' },
704
704
  { name: 'codex', usage: 'sks codex compatibility|version|doctor|schema|0.142 [--json]', description: 'Check Codex CLI rust-v0.142.0 compatibility, installed version, 0.142 manifest/capability evidence, inherited legacy baselines, and vendored hook schema snapshot freshness.' },
705
- { name: 'codex-app', usage: 'sks codex-app [check|product-design|product-design --check-only|ensure-product-design|chrome-extension|pat status|remote-control]', description: 'Check Codex App install, Product Design plugin auto-install readiness, Codex Chrome Extension web verification readiness, PAT-safe status, first-party MCP/plugin readiness, and Codex CLI 0.130.0+ remote-control availability.' },
705
+ { name: 'codex-app', usage: 'sks codex-app [check|glm-profile install|set-openrouter-key --api-key-stdin|product-design|chrome-extension|pat status|remote-control]', description: 'Check Codex App install, GLM/OpenRouter model profile visibility, codex-lb key-entry guidance, Product Design plugin readiness, Codex Chrome Extension web verification readiness, PAT-safe status, first-party MCP/plugin readiness, and Codex CLI 0.130.0+ remote-control availability.' },
706
706
  { name: 'codex-native', usage: 'sks codex-native status|feature-broker|invocation-plan|init-deep [--json]', description: 'Inspect Codex Native feature broker readiness, invocation routing, pattern evidence, and managed memory setup.' },
707
707
  { name: 'hooks', usage: 'sks hooks explain|status|trust-report|replay|codex-validate|warning-check ... [--json]', description: 'Explain Codex hook events, validate vendored latest 10-event output schemas, replay fixtures, and enforce warning-zero SKS hook policies under the 0.134 compatibility matrix.' },
708
708
  { name: 'codex-lb', usage: 'sks codex-lb status|health|metrics|doctor|circuit|repair|setup ...', description: 'Configure, health-check, repair, and record circuit evidence for codex-lb provider auth without confusing ChatGPT OAuth and proxy keys.' },
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '4.7.0';
1
+ export const PACKAGE_VERSION = '4.7.3';
2
2
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { codexProviderModelUiStatus, formatCodexAppStatus } from '../core/codex-app.js';
6
+ import { GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from '../core/codex-app/glm-model-profile.js';
7
+ import { GLM_52_OPENROUTER_MODEL } from '../core/providers/glm/glm-52-settings.js';
8
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-app-provider-ui-'));
9
+ const home = path.join(tmp, 'home');
10
+ const cwd = path.join(tmp, 'project');
11
+ await fs.mkdir(path.join(home, '.codex'), { recursive: true });
12
+ await fs.mkdir(path.join(cwd, '.codex'), { recursive: true });
13
+ const missing = await codexProviderModelUiStatus({ home, cwd, env: { HOME: home } });
14
+ const missingText = formatCodexAppStatus(statusFixture(missing));
15
+ await fs.writeFile(path.join(home, '.codex', 'config.toml'), readyConfig(), 'utf8');
16
+ await fs.writeFile(path.join(home, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-codex-lb-fixture'\n", 'utf8');
17
+ const ready = await codexProviderModelUiStatus({
18
+ home,
19
+ cwd,
20
+ env: { HOME: home, OPENROUTER_API_KEY: 'sk-or-fixture' }
21
+ });
22
+ const readyText = formatCodexAppStatus(statusFixture(ready));
23
+ const ok = missing.ok === false
24
+ && missing.glm.exposed === false
25
+ && missing.codex_lb.key_entry_visible === true
26
+ && missing.ui_actions.includes('sks codex-app set-openrouter-key --api-key-stdin')
27
+ && missing.ui_actions.includes('sks codex-lb setup --host <domain> --api-key-stdin --yes')
28
+ && /Provider UI:\s*setup/.test(missingText)
29
+ && /GLM Model:\s*setup/.test(missingText)
30
+ && /codex-lb Key:\s*missing \(input: sks codex-lb setup/.test(missingText)
31
+ && ready.ok === true
32
+ && ready.glm.exposed === true
33
+ && ready.glm.model === GLM_52_OPENROUTER_MODEL
34
+ && ready.glm.profiles_present.length === GLM_CODEX_CONFIG_REASONING_PROFILES.length
35
+ && ready.codex_lb.provider_present === true
36
+ && ready.codex_lb.key_present === true
37
+ && /Provider UI:\s*ok/.test(readyText)
38
+ && readyText.includes(`GLM Model: ok ${GLM_52_OPENROUTER_MODEL}`)
39
+ && /codex-lb Key:\s*configured/.test(readyText)
40
+ && !JSON.stringify({ missing, ready, missingText, readyText }).includes('sk-codex-lb-fixture');
41
+ emit({
42
+ schema: 'sks.codex-app-provider-model-ui-check.v1',
43
+ ok,
44
+ missing,
45
+ ready,
46
+ secret_safe: !JSON.stringify({ missing, ready, missingText, readyText }).includes('sk-codex-lb-fixture'),
47
+ blockers: ok ? [] : ['codex_app_provider_model_ui_check_failed']
48
+ });
49
+ function readyConfig() {
50
+ return [
51
+ `[model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}]`,
52
+ 'name = "OpenRouter"',
53
+ 'base_url = "https://openrouter.ai/api/v1"',
54
+ 'wire_api = "responses"',
55
+ 'env_key = "OPENROUTER_API_KEY"',
56
+ 'requires_openai_auth = false',
57
+ '',
58
+ ...GLM_CODEX_CONFIG_REASONING_PROFILES.flatMap((profile) => [
59
+ `[profiles.${profile.id}]`,
60
+ `model_provider = "${GLM_CODEX_CONFIG_PROVIDER_ID}"`,
61
+ `model = "${GLM_52_OPENROUTER_MODEL}"`,
62
+ `model_reasoning_effort = "${profile.reasoning_effort}"`,
63
+ 'service_tier = "default"',
64
+ 'approval_policy = "on-request"',
65
+ ''
66
+ ]),
67
+ '[model_providers.codex-lb]',
68
+ 'name = "openai"',
69
+ 'base_url = "https://lb.example.test/backend-api/codex"',
70
+ 'wire_api = "responses"',
71
+ 'env_key = "CODEX_LB_API_KEY"',
72
+ 'supports_websockets = true',
73
+ 'requires_openai_auth = true',
74
+ ''
75
+ ].join('\n');
76
+ }
77
+ function statusFixture(providerModelUi) {
78
+ return {
79
+ ok: providerModelUi.ok,
80
+ app: { installed: true, path: '/Applications/Codex.app' },
81
+ codex_cli: { ok: true, version: '0.142.0' },
82
+ remote_control: { ok: true, min_version: '0.130.0', codex_cli: { version_number: '0.142.0' } },
83
+ features: {
84
+ checked: true,
85
+ required_flags_ok: true,
86
+ required_flags: {},
87
+ fast_mode_config: { ok: true, blockers: [] },
88
+ provider_model_ui: providerModelUi,
89
+ git_actions: { ok: true, blockers: [] },
90
+ browser_tool_ready: true,
91
+ browser_tool_source: 'fixture',
92
+ image_generation: true
93
+ },
94
+ plugins: {
95
+ default_plugins: { ok: true },
96
+ design_product: { ok: true, id: 'product-design' },
97
+ picker: { ok: true }
98
+ },
99
+ chrome_extension: { ok: true, blockers: [] },
100
+ mcp: {
101
+ has_computer_use: true,
102
+ computer_use_source: 'mcp_list',
103
+ has_browser_use: true,
104
+ browser_use_source: 'mcp_list'
105
+ },
106
+ guidance: []
107
+ };
108
+ }
109
+ function emit(report) {
110
+ console.log(JSON.stringify(report, null, 2));
111
+ if (!report.ok)
112
+ process.exitCode = 1;
113
+ }
114
+ //# sourceMappingURL=codex-app-provider-model-ui-check.js.map