sneakoscope 4.7.1 → 4.7.4

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 (36) hide show
  1. package/README.md +11 -11
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -1
  7. package/dist/cli/install-helpers.js +215 -57
  8. package/dist/commands/codex-lb.js +143 -7
  9. package/dist/commands/doctor.js +59 -2
  10. package/dist/core/codex-app/codex-app-fast-ui-repair.js +11 -1
  11. package/dist/core/codex-app/codex-app-restart.js +65 -0
  12. package/dist/core/codex-app/sks-menubar.js +445 -0
  13. package/dist/core/codex-app.js +239 -3
  14. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -2
  15. package/dist/core/codex-control/codex-task-runner.js +2 -2
  16. package/dist/core/codex-lb/codex-lb-setup.js +1 -1
  17. package/dist/core/commands/basic-cli.js +14 -7
  18. package/dist/core/doctor/doctor-dirty-planner.js +2 -0
  19. package/dist/core/doctor/doctor-readiness-matrix.js +4 -0
  20. package/dist/core/fsx.js +1 -1
  21. package/dist/core/imagegen/imagegen-capability.js +2 -2
  22. package/dist/core/init.js +1 -1
  23. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  24. package/dist/core/provider/provider-context.js +1 -1
  25. package/dist/core/routes.js +2 -2
  26. package/dist/core/update-check.js +67 -18
  27. package/dist/core/version.js +1 -1
  28. package/dist/scripts/codex-app-provider-model-ui-check.js +114 -0
  29. package/dist/scripts/codex-lb-fast-mode-truth-check.js +90 -0
  30. package/dist/scripts/codex-lb-setup-truthfulness-check.js +6 -1
  31. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +5 -1
  32. package/dist/scripts/gpt-image-2-real-file-smoke.js +2 -2
  33. package/dist/scripts/provider-context-config-toml-check.js +2 -2
  34. package/dist/scripts/sks-menubar-install-check.js +59 -0
  35. package/dist/scripts/update-default-command-check.js +28 -0
  36. package/package.json +5 -1
@@ -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,29 @@ 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
+ companion_supported: process.platform === 'darwin',
461
+ companion_position: 'right macOS menu bar status item',
462
+ companion_install_command: 'sks doctor --fix',
463
+ alternatives: [
464
+ 'sks doctor --fix',
465
+ 'sks codex-lb use-codex-lb',
466
+ 'sks codex-lb use-oauth',
467
+ 'sks codex-app set-openrouter-key --api-key-stdin',
468
+ 'codex://settings'
469
+ ],
470
+ evidence: [
471
+ 'Codex official docs expose settings deep links, slash commands, skills/plugins, and app-server/remote-control surfaces, not third-party native macOS menu insertion.'
472
+ ]
473
+ };
474
+ }
475
+ 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
476
  const lines = [];
447
477
  if (!appInstalled) {
448
478
  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 +533,28 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
503
533
  lines.push(`Codex App speed selector can be hidden or locked by config: ${fastModeConfig.blockers.join(', ')}.`);
504
534
  lines.push('Run: sks doctor --fix');
505
535
  }
536
+ if (providerModelUi?.glm?.exposed && providerModelUi.glm.openrouter_key_present) {
537
+ lines.push(`GLM model selector profile is exposed: ${providerModelUi.glm.model} via profiles ${providerModelUi.glm.profiles_present.join(', ')}.`);
538
+ }
539
+ else if (providerModelUi?.glm?.exposed) {
540
+ lines.push(`GLM model selector profile is exposed for ${providerModelUi.glm.model}, but the OpenRouter key is missing.`);
541
+ lines.push(`Run: ${providerModelUi.glm.key_command}; restart Codex App if the model picker was already open.`);
542
+ }
543
+ else if (providerModelUi?.glm) {
544
+ lines.push(`GLM model selector profile is not exposed yet: ${providerModelUi.glm.blockers.join(', ') || 'profile setup required'}.`);
545
+ lines.push(`Run: ${providerModelUi.glm.key_command}; then ${providerModelUi.glm.install_command}; restart Codex App if the model picker was already open.`);
546
+ }
547
+ 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) {
548
+ lines.push(`codex-lb key entry path is visible and configured; update command ${providerModelUi.codex_lb.set_key_command}.`);
549
+ }
550
+ else if (providerModelUi?.codex_lb?.key_entry_visible) {
551
+ lines.push(`codex-lb key entry path is visible, but setup is incomplete: ${providerModelUi.codex_lb.blockers.join(', ') || 'setup required'}.`);
552
+ lines.push(`Run: ${providerModelUi.codex_lb.setup_command} or ${providerModelUi.codex_lb.set_key_command}.`);
553
+ }
554
+ else if (providerModelUi?.codex_lb) {
555
+ lines.push(`codex-lb key entry path is not exposed yet: ${providerModelUi.codex_lb.blockers.join(', ') || 'setup required'}.`);
556
+ lines.push(`Run: ${providerModelUi.codex_lb.setup_command} or ${providerModelUi.codex_lb.set_key_command}.`);
557
+ }
506
558
  if (!gitActions?.ok) {
507
559
  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
560
  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 +562,9 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
510
562
  else {
511
563
  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
564
  }
565
+ if (nativeSksMenu?.supported === false) {
566
+ lines.push('Codex App does not expose a documented third-party API for inserting SKS between Window and Help, so SKS uses a right-side macOS menu bar companion instead. Run `sks doctor --fix` to install or refresh it.');
567
+ }
513
568
  if (appInstalled && (!computerUseReady || !browserToolReady)) {
514
569
  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
570
  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 +606,10 @@ export function formatCodexAppStatus(status, { includeRaw = false } = {}) {
551
606
  `Remote Ctrl: ${status.remote_control?.ok ? 'ok' : 'missing'}${status.remote_control?.codex_cli?.version_number ? ` min ${status.remote_control.min_version}` : ''}`,
552
607
  `App Flags: ${status.features?.required_flags_ok ? 'ok' : `missing ${missingRequiredFeatureFlags(status.features?.required_flags).join(', ') || 'required flags'}`}`,
553
608
  `Fast UI: ${status.features?.fast_mode_config?.ok ? 'ok' : `locked ${(status.features?.fast_mode_config?.blockers || []).join(', ') || 'config'}`}`,
609
+ `SKS Menu: ${sksNativeMenuSummary(status.app?.sks_menu)}`,
610
+ `Provider UI:${providerModelUiSummary(status.features?.provider_model_ui)}`,
611
+ `GLM Model: ${glmModelUiSummary(status.features?.provider_model_ui?.glm)}`,
612
+ `codex-lb Key:${codexLbKeyUiSummary(status.features?.provider_model_ui?.codex_lb)}`,
554
613
  `Default Plugins:${status.plugins?.default_plugins?.ok ? ' ok' : ` missing ${defaultPluginMissingSummary(status.plugins?.default_plugins) || 'plugin install/config'}`}`,
555
614
  `Product Design:${productDesignStatusSummary(status.plugins?.design_product)}`,
556
615
  `Plugin Picker:${status.plugins?.picker?.ok ? ' ok' : ` blocked ${pluginPickerBlockers(status).join(', ') || 'config'}`}`,
@@ -817,6 +876,134 @@ async function codexFastModeConfigStatus(opts = {}) {
817
876
  blockers
818
877
  };
819
878
  }
879
+ export async function codexProviderModelUiStatus(opts = {}) {
880
+ const inputEnv = opts.env || process.env;
881
+ const home = opts.home || inputEnv.HOME || os.homedir();
882
+ const cwd = opts.cwd || process.cwd();
883
+ const env = { ...inputEnv, HOME: String(opts.home || inputEnv.HOME || home) };
884
+ const globalConfigPath = opts.configPath || path.join(home || '', '.codex', 'config.toml');
885
+ const projectConfigPath = path.join(cwd || '', '.codex', 'config.toml');
886
+ const globalConfig = await readTextIfExists(globalConfigPath);
887
+ const projectConfig = path.resolve(projectConfigPath) === path.resolve(globalConfigPath)
888
+ ? ''
889
+ : await readTextIfExists(projectConfigPath);
890
+ const configText = `${globalConfig}\n${projectConfig}`;
891
+ const openRouterKey = await resolveOpenRouterApiKey({ env }).catch((err) => ({
892
+ key: null,
893
+ source: null,
894
+ key_preview: null,
895
+ blockers: ['openrouter_key_lookup_failed'],
896
+ warnings: [err?.message || 'openrouter key lookup failed']
897
+ }));
898
+ const codexLbEnvFilePath = opts.codexLbEnvPath || codexLbEnvPath(home);
899
+ const codexLbEnvText = await readTextIfExists(codexLbEnvFilePath);
900
+ const codexLbApiKeySource = String(env.CODEX_LB_API_KEY || '').trim()
901
+ ? 'process.env'
902
+ : parseShellEnvValue(codexLbEnvText, 'CODEX_LB_API_KEY')
903
+ ? 'env-file'
904
+ : 'missing';
905
+ const codexLbBaseUrlSource = String(env.CODEX_LB_BASE_URL || '').trim()
906
+ ? 'process.env'
907
+ : parseShellEnvValue(codexLbEnvText, 'CODEX_LB_BASE_URL')
908
+ ? 'env-file'
909
+ : 'missing';
910
+ const openrouterProvider = tomlTableAny(configText, [
911
+ `model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}`,
912
+ `model_providers."${GLM_CODEX_CONFIG_PROVIDER_ID}"`
913
+ ]);
914
+ const glmProviderPresent = Boolean(openrouterProvider);
915
+ const profilesPresent = [];
916
+ const profilesMissing = [];
917
+ const glmProfileBlockers = [];
918
+ for (const profile of GLM_CODEX_CONFIG_REASONING_PROFILES) {
919
+ const body = tomlTableAny(configText, [`profiles.${profile.id}`, `profiles."${profile.id}"`]);
920
+ if (!body) {
921
+ profilesMissing.push(profile.id);
922
+ glmProfileBlockers.push(`glm_profile_missing:${profile.id}`);
923
+ continue;
924
+ }
925
+ profilesPresent.push(profile.id);
926
+ if (!hasTomlString(body, 'model_provider', GLM_CODEX_CONFIG_PROVIDER_ID))
927
+ glmProfileBlockers.push(`glm_profile_provider_invalid:${profile.id}`);
928
+ if (!hasTomlString(body, 'model', GLM_52_OPENROUTER_MODEL))
929
+ glmProfileBlockers.push(`glm_profile_model_invalid:${profile.id}`);
930
+ if (!hasTomlString(body, 'model_reasoning_effort', profile.reasoning_effort))
931
+ glmProfileBlockers.push(`glm_profile_reasoning_invalid:${profile.id}`);
932
+ }
933
+ const codexLbProvider = tomlTableAny(configText, ['model_providers.codex-lb', 'model_providers."codex-lb"']);
934
+ const codexLbProviderPresent = Boolean(codexLbProvider);
935
+ const codexLbProviderContractOk = codexLbProviderPresent
936
+ && hasTomlString(codexLbProvider, 'name', 'openai')
937
+ && hasTomlString(codexLbProvider, 'wire_api', 'responses')
938
+ && hasTomlString(codexLbProvider, 'env_key', 'CODEX_LB_API_KEY')
939
+ && hasTomlBoolean(codexLbProvider, 'supports_websockets', true)
940
+ && hasTomlBoolean(codexLbProvider, 'requires_openai_auth', true);
941
+ const codexLbSelectedDefault = /(?:^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevelToml(globalConfig));
942
+ const glmBlockers = [
943
+ ...(glmProviderPresent ? [] : ['glm_openrouter_provider_missing']),
944
+ ...glmProfileBlockers,
945
+ ...(openRouterKey.key ? [] : ['openrouter_key_missing'])
946
+ ];
947
+ const codexLbBlockers = [
948
+ ...(codexLbProviderPresent ? [] : ['codex_lb_provider_missing']),
949
+ ...(codexLbProviderPresent && !codexLbProviderContractOk ? ['codex_lb_provider_contract_drift'] : []),
950
+ ...(codexLbApiKeySource !== 'missing' ? [] : ['codex_lb_api_key_missing']),
951
+ ...(codexLbBaseUrlSource !== 'missing' ? [] : ['codex_lb_base_url_missing'])
952
+ ];
953
+ const blockers = [...glmBlockers, ...codexLbBlockers];
954
+ const keyCommand = 'sks codex-app set-openrouter-key --api-key-stdin';
955
+ const installCommand = 'sks codex-app glm-profile install';
956
+ const setupCommand = 'sks codex-lb setup --host <domain> --api-key-stdin --yes';
957
+ const setKeyCommand = 'sks codex-lb set-key --api-key-stdin';
958
+ return {
959
+ schema: 'sks.codex-app-provider-model-ui.v1',
960
+ ok: blockers.length === 0,
961
+ checked: true,
962
+ status: blockers.length === 0 ? 'ready' : 'setup_required',
963
+ config_paths: {
964
+ codex_config: globalConfigPath,
965
+ project_config: projectConfig ? projectConfigPath : null,
966
+ codex_lb_env: codexLbEnvFilePath
967
+ },
968
+ glm: {
969
+ exposed: glmProviderPresent && profilesMissing.length === 0 && glmProfileBlockers.length === 0,
970
+ provider_present: glmProviderPresent,
971
+ provider: GLM_CODEX_CONFIG_PROVIDER_ID,
972
+ model: GLM_52_OPENROUTER_MODEL,
973
+ profiles_present: profilesPresent,
974
+ profiles_missing: profilesMissing,
975
+ openrouter_key_present: Boolean(openRouterKey.key),
976
+ openrouter_key_source: openRouterKey.source || null,
977
+ openrouter_key_preview: openRouterKey.key_preview || null,
978
+ key_command: keyCommand,
979
+ install_command: installCommand,
980
+ doctor_command: 'sks codex-app glm-profile doctor',
981
+ blockers: glmBlockers
982
+ },
983
+ codex_lb: {
984
+ key_entry_visible: true,
985
+ provider_present: codexLbProviderPresent,
986
+ provider_contract_ok: codexLbProviderContractOk,
987
+ selected_default: codexLbSelectedDefault,
988
+ key_present: codexLbApiKeySource !== 'missing',
989
+ api_key_source: codexLbApiKeySource,
990
+ base_url_present: codexLbBaseUrlSource !== 'missing',
991
+ base_url_source: codexLbBaseUrlSource,
992
+ setup_command: setupCommand,
993
+ set_key_command: setKeyCommand,
994
+ status_command: 'sks codex-lb status',
995
+ blockers: codexLbBlockers
996
+ },
997
+ ui_actions: [
998
+ keyCommand,
999
+ installCommand,
1000
+ setupCommand,
1001
+ setKeyCommand,
1002
+ 'sks codex-app status'
1003
+ ],
1004
+ blockers
1005
+ };
1006
+ }
820
1007
  async function readTextIfExists(file) {
821
1008
  try {
822
1009
  return await fsp.readFile(file, 'utf8');
@@ -876,6 +1063,38 @@ function productDesignStatusSummary(status = {}) {
876
1063
  return ' app-server unverified';
877
1064
  return ' not checked';
878
1065
  }
1066
+ function providerModelUiSummary(status = {}) {
1067
+ if (!status?.checked)
1068
+ return ' not checked';
1069
+ if (status.ok)
1070
+ return ' ok (GLM + codex-lb)';
1071
+ return ` setup ${(status.blockers || []).join(', ') || 'required'}`;
1072
+ }
1073
+ function sksNativeMenuSummary(status = {}) {
1074
+ if (!status)
1075
+ return 'not checked';
1076
+ if (status.supported)
1077
+ return 'ok';
1078
+ return `${status.status || 'unsupported'} (use CLI/settings)`;
1079
+ }
1080
+ function glmModelUiSummary(status = {}) {
1081
+ if (!status)
1082
+ return 'not checked';
1083
+ if (status.exposed) {
1084
+ const key = status.openrouter_key_present ? `key ${status.openrouter_key_source || 'present'}` : 'key missing';
1085
+ return `ok ${status.model} (${status.profiles_present?.length || 0} profiles, ${key})`;
1086
+ }
1087
+ return `setup ${(status.blockers || []).join(', ') || 'profile missing'}; run ${status.key_command || 'sks codex-app set-openrouter-key --api-key-stdin'}`;
1088
+ }
1089
+ function codexLbKeyUiSummary(status = {}) {
1090
+ if (!status)
1091
+ return ' not checked';
1092
+ if (status.key_present && status.provider_contract_ok !== false)
1093
+ return ` configured (${status.api_key_source || 'present'})`;
1094
+ if (status.key_present)
1095
+ return ` setup provider drift; run ${status.setup_command || 'sks codex-lb setup --host <domain> --api-key-stdin --yes'}`;
1096
+ return ` missing (input: ${status.setup_command || 'sks codex-lb setup --host <domain> --api-key-stdin --yes'})`;
1097
+ }
879
1098
  function defaultPluginMissingSummary(defaultPlugins = {}) {
880
1099
  return [
881
1100
  ...(defaultPlugins?.missing_installed || []),
@@ -891,6 +1110,23 @@ function tomlTable(text = '', table = '') {
891
1110
  const re = new RegExp(`(?:^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`);
892
1111
  return String(text || '').match(re)?.[1] || '';
893
1112
  }
1113
+ function tomlTableAny(text = '', tables = []) {
1114
+ for (const table of tables) {
1115
+ const body = tomlTable(text, table);
1116
+ if (body)
1117
+ return body;
1118
+ }
1119
+ return '';
1120
+ }
1121
+ function hasTomlString(text = '', key = '', value = '') {
1122
+ const re = new RegExp(`(?:^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?(?=\\n|$)`);
1123
+ return re.test(String(text || ''));
1124
+ }
1125
+ function hasTomlBoolean(text = '', key = '', value) {
1126
+ const expected = value ? 'true' : 'false';
1127
+ const re = new RegExp(`(?:^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*${expected}\\s*(?:#.*)?(?=\\n|$)`);
1128
+ return re.test(String(text || ''));
1129
+ }
894
1130
  function escapeRegExp(text = '') {
895
1131
  return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
896
1132
  }
@@ -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"' });
@@ -113,24 +113,29 @@ export async function updateCheckCommand(args = []) {
113
113
  return printJson(result);
114
114
  console.log(`${sksTextLogo()}\n\n${formatSksUpdateCheckText(result)}`);
115
115
  }
116
- export async function updateCommand(sub = 'check', args = []) {
117
- const action = String(sub || 'check').toLowerCase();
116
+ export async function updateCommand(sub = 'now', args = []) {
117
+ let action = String(sub || 'now').toLowerCase();
118
+ let effectiveArgs = args;
119
+ if (action.startsWith('-')) {
120
+ effectiveArgs = [String(sub), ...args];
121
+ action = 'now';
122
+ }
118
123
  if (action === 'check' || action === 'status')
119
- return updateCheckCommand(args);
124
+ return updateCheckCommand(effectiveArgs);
120
125
  if (action !== 'now') {
121
- console.error('Usage: sks update check|now [--version <version>] [--json] [--dry-run]');
126
+ console.error('Usage: sks update [check|now] [--version <version>] [--json] [--dry-run]');
122
127
  process.exitCode = 1;
123
128
  return;
124
129
  }
125
130
  const root = await projectRoot();
126
131
  const result = await withSecretPreservationGuard(root, 'update-now', async () => runSksUpdateNow({
127
- version: valueAfter(args, '--version') || valueAfter(args, '-v'),
128
- dryRun: flag(args, '--dry-run'),
132
+ version: valueAfter(effectiveArgs, '--version') || valueAfter(effectiveArgs, '-v'),
133
+ dryRun: flag(effectiveArgs, '--dry-run'),
129
134
  projectRoot: root,
130
135
  timeoutMs: 10 * 60 * 1000,
131
136
  maxOutputBytes: 128 * 1024
132
137
  }));
133
- if (flag(args, '--json'))
138
+ if (flag(effectiveArgs, '--json'))
134
139
  return printJson(result);
135
140
  console.log(`${sksTextLogo()}\n`);
136
141
  console.log(`SKS update ${result.status}`);
@@ -144,6 +149,8 @@ export async function updateCommand(sub = 'check', args = []) {
144
149
  console.log(`New version: ${result.new_version}`);
145
150
  if (result.project_receipt)
146
151
  console.log(`Migration receipt: ${result.project_receipt.root} (${result.migration_current ? 'current' : 'not current'})`);
152
+ if (result.sks_menubar)
153
+ console.log(`SKS menu bar: ${result.sks_menubar.status}${result.sks_menubar.app_path ? ` (${result.sks_menubar.app_path})` : ''}`);
147
154
  for (const stage of result.stages || [])
148
155
  console.log(`Stage ${stage.id}: ${stage.ok ? 'ok' : 'failed'} ${stage.status}`);
149
156
  if (result.error)
@@ -89,6 +89,8 @@ function phaseInputFiles(id) {
89
89
  return ['src/core/doctor/codex-startup-config-repair.ts', '.codex/config.toml'];
90
90
  if (id.includes('supabase'))
91
91
  return ['src/core/doctor/supabase-mcp-repair.ts', '.codex/config.toml'];
92
+ if (id.includes('menubar'))
93
+ return ['src/core/codex-app/sks-menubar.ts', 'src/commands/doctor.ts', 'package.json'];
92
94
  if (id.includes('skill'))
93
95
  return ['.agents/skills', 'src/scripts/skill-registry-ledger-check.ts'];
94
96
  if (id.includes('native'))
@@ -60,6 +60,8 @@ export function buildDoctorReadinessMatrix(input = {}) {
60
60
  blockers.add('codex_app_fast_ui_repair_requires_confirmation');
61
61
  if (input.codex_app_ui?.fast_selector === 'repaired')
62
62
  warnings.add('codex_app_fast_selector_repaired_restart_app_if_needed');
63
+ if (input.sks_menubar?.ok === false)
64
+ warnings.add(`sks_menubar_${input.sks_menubar?.status || 'blocked'}`);
63
65
  const codex0138Doctor = input.codex_0138_doctor || null;
64
66
  if (codex0138Doctor?.ok === false)
65
67
  for (const blocker of normalizeList(codex0138Doctor.blockers))
@@ -148,6 +150,7 @@ export function buildDoctorReadinessMatrix(input = {}) {
148
150
  codex_app_harness_matrix: codexAppHarness,
149
151
  fast_mode_ready: input.fast_mode_ready !== false,
150
152
  codex_app_ui: input.codex_app_ui || null,
153
+ sks_menubar: input.sks_menubar || null,
151
154
  hooks_ready: input.hooks_ready !== false,
152
155
  codex_app_ready: input.codex_app?.ok === true,
153
156
  codex_app_required_for_cli: false,
@@ -205,6 +208,7 @@ function buildRepairReadiness(input = {}) {
205
208
  add('context7_repair', input.context7_repair, true);
206
209
  add('context7_mcp_repair', input.context7_mcp_repair, true);
207
210
  add('supabase_mcp_repair', input.supabase_mcp_repair, input.supabase_mcp_repair?.ready_blocking === true);
211
+ add('sks_menubar', input.sks_menubar, false);
208
212
  add('command_alias_cleanup', input.command_aliases, true);
209
213
  add('native_capability_repair', input.doctor_native_capability, false);
210
214
  if (input.doctor_fix_transaction) {
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.4';
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
@@ -643,7 +643,7 @@ export async function initProject(root, opts = {}) {
643
643
  next = shouldSelectCodexLb
644
644
  ? upsertTopLevelTomlString(next, 'model_provider', 'codex-lb')
645
645
  : removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
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 = 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`);
647
647
  return `${next.trim()}\n`;
648
648
  }
649
649
  function parseCodexLbEnvKey(text = '') {
@@ -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.4';
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();
@@ -699,10 +699,10 @@ export const COMMAND_CATALOG = [
699
699
  { name: 'quickstart', usage: 'sks quickstart', description: 'Show the shortest safe setup and verification flow.' },
700
700
  { name: 'bootstrap', usage: 'sks bootstrap [--install-scope global|project] [--local-only] [--json]', description: 'Initialize the current project, install SKS Codex App files/skills, check Context7/Codex App/Zellij, and print ready true/false.' },
701
701
  { name: 'root', usage: 'sks root [--json]', description: 'Show whether SKS is using a project root or the per-user global SKS runtime root.' },
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.' },
702
+ { name: 'update', usage: 'sks update [check|now] [--version <version>] [--json] [--dry-run]', description: 'Check npm for the latest SKS release and update the global package by default; use `check` for status-only.' },
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.' },