sneakoscope 5.6.1 → 5.7.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.
@@ -15,6 +15,8 @@ const MENU_ITEMS = [
15
15
  'Use ChatGPT OAuth',
16
16
  'Set codex-lb Domain and Key',
17
17
  'Set OpenRouter Key and GLM Profiles',
18
+ 'Fast Mode On',
19
+ 'Fast Mode Off',
18
20
  'Fast Check',
19
21
  'SKS Version Check',
20
22
  'Update SKS Now',
@@ -63,6 +65,7 @@ export async function installSksMenuBar(opts = {}) {
63
65
  const nextActions = defaultNextActions();
64
66
  let secretEnvCleanup;
65
67
  let codexBundleId = null;
68
+ let lastTargetCheck;
66
69
  if (process.platform !== 'darwin') {
67
70
  const result = {
68
71
  schema: 'sks.codex-app-sks-menubar.v1',
@@ -170,15 +173,11 @@ export async function installSksMenuBar(opts = {}) {
170
173
  actionScriptPath: paths.action_script_path,
171
174
  warnings
172
175
  });
173
- const previousActionScript = await readText(paths.action_script_path, '');
174
- if (!target.exists && previousActionScript)
175
- actions.push('kept previous action script because resolved SKS entry was missing');
176
- if (!target.exists && !previousActionScript) {
177
- return await blockedResult('sks_entry_unresolved', `Resolved SKS entry does not exist: ${target.resolved || target.packaged}`);
178
- }
179
- const actionScript = target.used_previous_script
180
- ? previousActionScript
181
- : actionScriptSource({ nodeBin: process.execPath, sksEntry: target.resolved || target.packaged });
176
+ lastTargetCheck = target;
177
+ if (!target.exists) {
178
+ return await blockedResult('sks_entry_unresolved', `Resolved SKS entry does not exist: ${target.requested || target.resolved || target.packaged}`);
179
+ }
180
+ const actionScript = actionScriptSource({ nodeBin: process.execPath, sksEntry: target.resolved || target.packaged });
182
181
  const swiftSource = swiftMenuSource({
183
182
  actionScriptPath: paths.action_script_path,
184
183
  buildStampPath: paths.build_stamp_path,
@@ -201,7 +200,9 @@ export async function installSksMenuBar(opts = {}) {
201
200
  };
202
201
  const previousStamp = await readJson(paths.build_stamp_path, null);
203
202
  const appInstalled = await exists(paths.executable_path);
204
- const stampMatches = appInstalled && buildStampEquals(previousStamp, stamp);
203
+ const currentActionScript = await readText(paths.action_script_path, '');
204
+ const actionScriptCurrent = currentActionScript === actionScript;
205
+ const stampMatches = appInstalled && buildStampEquals(previousStamp, stamp) && actionScriptCurrent;
205
206
  const binaryStable = appInstalled
206
207
  && previousStamp?.schema === stamp.schema
207
208
  && previousStamp.package_version === stamp.package_version
@@ -213,7 +214,7 @@ export async function installSksMenuBar(opts = {}) {
213
214
  actions.push('menubar_up_to_date');
214
215
  }
215
216
  else {
216
- if (!target.used_previous_script && await readText(paths.action_script_path, '') !== actionScript) {
217
+ if (currentActionScript !== actionScript) {
217
218
  await writeTextAtomic(paths.action_script_path, actionScript);
218
219
  actions.push(`wrote ${paths.action_script_path}`);
219
220
  }
@@ -342,6 +343,7 @@ export async function installSksMenuBar(opts = {}) {
342
343
  menu_items: MENU_ITEMS,
343
344
  actions,
344
345
  launch: { requested: false, method: 'none', ok: false, error: detail || reason },
346
+ target_check: lastTargetCheck,
345
347
  build_stamp: null,
346
348
  tcc_automation_status: 'unknown',
347
349
  secret_env_cleanup: secretEnvCleanup,
@@ -394,7 +396,7 @@ async function writeDefaultMenuBarConfig(configPath, codexBundleId) {
394
396
  const previous = await readMenuBarConfig(configPath);
395
397
  const config = {
396
398
  schema: 'sks.sks-menubar-config.v1',
397
- codex_bundle_id: codexBundleId,
399
+ codex_bundle_id: codexBundleId || previous.codex_bundle_id,
398
400
  quit_with_codex: previous.quit_with_codex === true
399
401
  };
400
402
  await writeJsonAtomic(configPath, config);
@@ -691,16 +693,15 @@ async function resolveSksEntryForInstall(input) {
691
693
  if (projectLocal) {
692
694
  input.warnings.push('sks_entry_project_local');
693
695
  }
694
- const usedPreviousScript = !existsResolved && await exists(input.actionScriptPath);
695
- if (!existsResolved && usedPreviousScript)
696
- input.warnings.push('sks_entry_unresolved_kept_previous_script');
696
+ if (!existsResolved && await exists(input.actionScriptPath))
697
+ input.warnings.push('sks_entry_unresolved_stale_action_script_not_reused');
697
698
  return {
698
699
  requested: input.explicit ? path.resolve(input.explicit) : null,
699
700
  resolved: existsResolved ? resolved : null,
700
701
  packaged,
701
702
  exists: existsResolved,
702
703
  project_local: projectLocal,
703
- used_previous_script: usedPreviousScript
704
+ used_previous_script: false
704
705
  };
705
706
  }
706
707
  export function actionScriptSource(input) {
@@ -960,6 +961,11 @@ func promptChoice(title: String, message: String, options: [String]) -> String?
960
961
  return popup.titleOfSelectedItem
961
962
  }
962
963
 
964
+ func parseJsonObject(_ text: String) -> [String: Any]? {
965
+ guard let data = text.data(using: .utf8) else { return nil }
966
+ return (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any]
967
+ }
968
+
963
969
  func runProcess(_ executable: String, _ args: [String] = [], stdinText: String? = nil, completion: ((Int32, String) -> Void)? = nil) {
964
970
  let process = Process()
965
971
  let output = Pipe()
@@ -1052,6 +1058,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
1052
1058
  var statusLineItem: NSMenuItem!
1053
1059
  var codexLbItem: NSMenuItem!
1054
1060
  var oauthItem: NSMenuItem!
1061
+ var fastModeOnItem: NSMenuItem!
1062
+ var fastModeOffItem: NSMenuItem!
1055
1063
  var timer: Timer?
1056
1064
  var busy = false
1057
1065
  var lastFailure = false
@@ -1077,6 +1085,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
1077
1085
  add(menu, "Set codex-lb Domain and Key", #selector(setCodexLbDomainAndKey))
1078
1086
  menu.addItem(NSMenuItem.separator())
1079
1087
  add(menu, "Set OpenRouter Key and GLM Profiles", #selector(setOpenRouterKey))
1088
+ fastModeOnItem = add(menu, "Fast Mode On", #selector(fastModeOn))
1089
+ fastModeOffItem = add(menu, "Fast Mode Off", #selector(fastModeOff))
1080
1090
  add(menu, "Fast Check", #selector(fastCheck))
1081
1091
  add(menu, "SKS Version Check", #selector(sksVersionCheck))
1082
1092
  add(menu, "Update SKS Now", #selector(updateSksNow))
@@ -1096,6 +1106,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
1096
1106
  func menuWillOpen(_ menu: NSMenu) {
1097
1107
  updateState()
1098
1108
  updateAuthModeChecks()
1109
+ updateFastModeChecks()
1099
1110
  }
1100
1111
 
1101
1112
  func configureStatusButton(_ button: NSStatusBarButton, title: String) {
@@ -1197,6 +1208,15 @@ ${codexLifecycleSource}
1197
1208
  runSksCapture(args, title: title, stdinText: stdinText, notify: true, completion: completion)
1198
1209
  }
1199
1210
 
1211
+ func runSksSilent(_ args: [String], completion: ((Int32, String) -> Void)? = nil) {
1212
+ runProcess(actionScript, args) { code, output in
1213
+ let redacted = redactSecrets(output)
1214
+ DispatchQueue.main.async {
1215
+ completion?(code, redacted)
1216
+ }
1217
+ }
1218
+ }
1219
+
1200
1220
  func startSksDetached(_ args: [String], title: String) {
1201
1221
  let process = Process()
1202
1222
  process.executableURL = URL(fileURLWithPath: actionScript)
@@ -1215,15 +1235,30 @@ ${codexLifecycleSource}
1215
1235
  }
1216
1236
 
1217
1237
  func updateAuthModeChecks() {
1218
- runSksCapture(["codex-lb", "status", "--json"], title: "SKS Auth Status", notify: false) { [weak self] code, output in
1238
+ runSksSilent(["codex-lb", "status", "--json"]) { [weak self] code, output in
1219
1239
  guard let self = self else { return }
1220
- let lower = output.lowercased()
1221
- let codexLbActive = code == 0 && (lower.contains(#""configured": true"#) || lower.contains(#""model_provider": "codex-lb""#) || lower.contains(#""mode": "codex-lb""#))
1240
+ let json = parseJsonObject(output)
1241
+ let selected = json?["selected"] as? Bool == true
1242
+ let provider = (json?["model_provider"] as? String)?.lowercased() == "codex-lb"
1243
+ let mode = (json?["mode"] as? String)?.lowercased() == "codex-lb"
1244
+ let codexLbActive = code == 0 && (selected || provider || mode)
1222
1245
  self.codexLbItem.state = codexLbActive ? .on : .off
1223
1246
  self.oauthItem.state = codexLbActive ? .off : .on
1224
1247
  }
1225
1248
  }
1226
1249
 
1250
+ func updateFastModeChecks() {
1251
+ runSksSilent(["fast-mode", "status", "--json"]) { [weak self] code, output in
1252
+ guard let self = self else { return }
1253
+ let json = parseJsonObject(output)
1254
+ let fastMode = json?["fast_mode"] as? Bool == true
1255
+ let serviceTier = (json?["service_tier"] as? String)?.lowercased()
1256
+ let fastActive = code == 0 && (fastMode || serviceTier == "fast" || serviceTier == "priority")
1257
+ self.fastModeOnItem.state = fastActive ? .on : .off
1258
+ self.fastModeOffItem.state = fastActive ? .off : .on
1259
+ }
1260
+ }
1261
+
1227
1262
  @objc func useCodexLb() {
1228
1263
  runSksBackground(["codex-lb", "use-codex-lb", "--json"], title: "Use codex-lb") { [weak self] code, _ in
1229
1264
  if code == 0 { self?.updateAuthModeChecks() }
@@ -1249,6 +1284,18 @@ ${codexLifecycleSource}
1249
1284
  runSksBackground(["codex-app", "set-openrouter-key", "--api-key-stdin", "--json"], title: "Set OpenRouter Key", stdinText: key + "\\n")
1250
1285
  }
1251
1286
 
1287
+ @objc func fastModeOn() {
1288
+ runSksBackground(["fast-mode", "on", "--json"], title: "Fast Mode On") { [weak self] code, _ in
1289
+ if code == 0 { self?.updateFastModeChecks() }
1290
+ }
1291
+ }
1292
+
1293
+ @objc func fastModeOff() {
1294
+ runSksBackground(["fast-mode", "off", "--json"], title: "Fast Mode Off") { [weak self] code, _ in
1295
+ if code == 0 { self?.updateFastModeChecks() }
1296
+ }
1297
+ }
1298
+
1252
1299
  @objc func fastCheck() {
1253
1300
  runSksBackground(["codex-lb", "fast-check"], title: "SKS Fast Check")
1254
1301
  }
@@ -256,14 +256,18 @@ async function executeRouteCommand(root, route, prompt, { auto = false } = {}) {
256
256
  // that never saw the prompt cannot claim to have addressed it.
257
257
  const isMockFallback = commandArgs.includes('--mock');
258
258
  const promptDelivered = Boolean(prompt) && commandArgs.includes(prompt);
259
+ const deterministicRoute = isSafeDeterministicRoute(route.command);
259
260
  const result = await runSks(root, commandArgs);
260
261
  return routeExecutionResult(route, ['sks', ...commandArgs].join(' '), result, {
261
262
  okStatus: isMockFallback ? 'verified_partial' : 'completed',
262
263
  trustStatus: isMockFallback ? 'mock_only' : 'verified_partial',
263
- executionKind: isMockFallback ? 'mock_safe' : 'live_route',
264
+ executionKind: isMockFallback ? 'mock_safe' : deterministicRoute ? 'safe_deterministic' : 'live_route',
264
265
  promptDelivered,
265
266
  });
266
267
  }
268
+ function isSafeDeterministicRoute(command) {
269
+ return new Set(['$DB', '$Wiki', '$Fast-Mode', '$with-local-llm-on', '$Commit', '$Commit-And-Push']).has(command);
270
+ }
267
271
  async function runAutoVerification(root, missionId) {
268
272
  const trust = await runSks(root, ['trust', 'validate', missionId, '--json']);
269
273
  const status = await runSks(root, ['status', '--json']);
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 = '5.6.1';
8
+ export const PACKAGE_VERSION = '5.7.0';
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() {
@@ -8,6 +8,7 @@ import { COMMANDS } from '../../cli/command-registry.js';
8
8
  import { reconcileSkills } from '../init/skills.js';
9
9
  import { codexHookTrustDoctor } from '../codex-hooks/codex-hook-trust-doctor.js';
10
10
  import { writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
11
+ import { REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
11
12
  export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
12
13
  export const INSTALLATION_EPOCH_SCHEMA = 'sks.installation-epoch.v1';
13
14
  export function installationEpochPath() {
@@ -343,53 +344,71 @@ async function runMenubarRetargetStage(root) {
343
344
  return { ok: true, status: 'ok', actions: ['menubar_action_script_absent'], blockers: [], warnings: [] };
344
345
  const desired = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
345
346
  const line = `SKS_ENTRY=${JSON.stringify(desired)}`;
347
+ const actions = [];
346
348
  const next = /^\s*SKS_ENTRY\s*=.*$/m.test(text)
347
349
  ? text.replace(/^\s*SKS_ENTRY\s*=.*$/m, line)
348
350
  : `${line}\n${text}`;
349
351
  if (next !== text) {
350
352
  await writeTextAtomic(actionScript, next);
351
- return { ok: true, status: 'ok', actions: ['retargeted_menubar_action_script'], blockers: [], warnings: [], detail: { action_script: actionScript } };
353
+ actions.push('retargeted_menubar_action_script');
354
+ }
355
+ const stat = await fsp.stat(actionScript).catch(() => null);
356
+ if (!stat || (stat.mode & 0o111) === 0) {
357
+ await fsp.chmod(actionScript, 0o755);
358
+ actions.push('restored_menubar_action_executable_bit');
352
359
  }
353
- return { ok: true, status: 'ok', actions: ['menubar_action_script_current'], blockers: [], warnings: [], detail: { action_script: actionScript } };
360
+ return {
361
+ ok: true,
362
+ status: 'ok',
363
+ actions: actions.length ? actions : ['menubar_action_script_current'],
364
+ blockers: [],
365
+ warnings: [],
366
+ detail: { action_script: actionScript }
367
+ };
354
368
  }
355
369
  async function runConfigFastModeNormalizeStage() {
356
370
  const configPath = path.join(os.homedir(), '.codex', 'config.toml');
357
371
  const text = await readText(configPath, null);
358
372
  if (typeof text !== 'string')
359
373
  return { ok: true, status: 'ok', actions: ['codex_config_absent'], blockers: [], warnings: [] };
360
- const table = text.match(/(^|\n)\[user\.fast_mode\][\s\S]*?(?=\n\[|$)/);
361
- const misplaced = table?.[0]?.match(/^\s*default_profile\s*=\s*"([^"]+)"\s*$/m)?.[1] || null;
362
- const hasTopLevel = /^\s*default_profile\s*=\s*"[^"]+"\s*$/m.test(text.slice(0, Math.max(0, text.search(/^\s*\[/m))));
363
- if (!misplaced)
364
- return { ok: true, status: 'ok', actions: ['fastmode_default_profile_current'], blockers: [], warnings: [] };
365
- let next = text.replace(/(^|\n)(\[user\.fast_mode\][\s\S]*?)(?=\n\[|$)/, (_match, prefix, block) => {
366
- const cleaned = String(block).split(/\r?\n/).filter((line) => !/^\s*default_profile\s*=/.test(line)).join('\n').trimEnd();
367
- return `${prefix}${cleaned}`;
368
- });
369
- if (!hasTopLevel)
370
- next = insertTopLevelTomlKey(next, `default_profile = ${JSON.stringify(misplaced)}`);
374
+ const normalized = normalizeLegacyFastModeConfigForUpdate(text);
375
+ if (normalized.text === ensureTrailingNewline(text)) {
376
+ return {
377
+ ok: true,
378
+ status: 'ok',
379
+ actions: ['fastmode_config_current'],
380
+ blockers: [],
381
+ warnings: [],
382
+ detail: { config_path: configPath, default_profile: normalized.defaultProfile }
383
+ };
384
+ }
371
385
  let guardResult = null;
372
- if (next !== text) {
373
- guardResult = await writeCodexConfigGuarded({
374
- configPath,
375
- before: text,
376
- mutate: () => `${next.trim()}\n`,
377
- cause: 'project-update-fastmode-default-profile',
378
- backupTag: 'project-update-fastmode-default-profile',
379
- preserveFastUiKeys: true
380
- });
381
- if (!guardResult.ok) {
382
- return {
383
- ok: false,
384
- status: 'failed',
385
- actions: ['normalize_fastmode_default_profile_blocked'],
386
- blockers: [`codex_config_guard:${guardResult.status}`],
387
- warnings: [],
388
- detail: { config_path: configPath, default_profile: misplaced, guard: guardResult }
389
- };
390
- }
386
+ guardResult = await writeCodexConfigGuarded({
387
+ configPath,
388
+ before: text,
389
+ mutate: () => normalized.text,
390
+ cause: 'project-update-fastmode-normalize',
391
+ backupTag: 'project-update-fastmode-normalize',
392
+ preserveFastUiKeys: true
393
+ });
394
+ if (!guardResult.ok) {
395
+ return {
396
+ ok: false,
397
+ status: 'failed',
398
+ actions: ['normalize_fastmode_config_blocked'],
399
+ blockers: [`codex_config_guard:${guardResult.status}`],
400
+ warnings: [],
401
+ detail: { config_path: configPath, default_profile: normalized.defaultProfile, guard: guardResult }
402
+ };
391
403
  }
392
- return { ok: true, status: 'ok', actions: ['normalized_fastmode_default_profile'], blockers: [], warnings: [], detail: { config_path: configPath, default_profile: misplaced, guard: guardResult } };
404
+ return {
405
+ ok: true,
406
+ status: 'ok',
407
+ actions: normalized.actions.length ? normalized.actions : ['fastmode_config_current'],
408
+ blockers: [],
409
+ warnings: [],
410
+ detail: { config_path: configPath, default_profile: normalized.defaultProfile, guard: guardResult }
411
+ };
393
412
  }
394
413
  async function runHookTrustRefreshStage(root) {
395
414
  const result = await codexHookTrustDoctor(root, { fix: true, managed: true, actual: true });
@@ -456,6 +475,132 @@ function insertTopLevelTomlKey(text, line) {
456
475
  return `${line}\n${raw}`.trim() + '\n';
457
476
  return `${raw.slice(0, firstTable).trimEnd()}\n${line}\n\n${raw.slice(firstTable).trimStart()}`.trim() + '\n';
458
477
  }
478
+ function normalizeLegacyFastModeConfigForUpdate(text) {
479
+ let next = String(text || '');
480
+ const actions = [];
481
+ const misplaced = tomlTableString(next, 'user.fast_mode', 'default_profile');
482
+ const topLevel = topLevelTomlString(next, 'default_profile');
483
+ if (misplaced) {
484
+ next = removeTomlTableKeyLocal(next, 'user.fast_mode', 'default_profile');
485
+ actions.push('normalized_fastmode_default_profile_location');
486
+ }
487
+ if (topLevel && topLevel !== 'sks-fast-high' && !tomlTableBlock(next, `profiles.${topLevel}`)) {
488
+ next = upsertTopLevelTomlStringLocal(next, 'default_profile', 'sks-fast-high');
489
+ actions.push('retargeted_missing_fastmode_default_profile_target');
490
+ }
491
+ else if (!topLevel && misplaced === 'sks-fast-high') {
492
+ next = insertTopLevelTomlKey(next, `default_profile = ${JSON.stringify(misplaced)}`);
493
+ actions.push('restored_fastmode_top_level_default_profile');
494
+ }
495
+ const beforeUi = next;
496
+ next = upsertTomlTableKeyIfAbsentLocal(next, 'user.fast_mode', 'visible = true');
497
+ next = upsertTomlTableKeyIfAbsentLocal(next, 'user.fast_mode', 'enabled = true');
498
+ if (next !== beforeUi)
499
+ actions.push('ensured_fastmode_ui_visibility');
500
+ const beforeProfile = next;
501
+ next = upsertTomlTableKeyLocal(next, 'profiles.sks-fast-high', `model = "${REQUIRED_CODEX_MODEL}"`);
502
+ next = upsertTomlTableKeyLocal(next, 'profiles.sks-fast-high', 'service_tier = "fast"');
503
+ if (next !== beforeProfile)
504
+ actions.push('normalized_sks_fast_high_profile');
505
+ return { text: ensureTrailingNewline(next), actions, defaultProfile: misplaced || topLevel };
506
+ }
507
+ function tomlTableString(text, table, key) {
508
+ const block = tomlTableBlock(text, table);
509
+ const match = block?.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, 'm'));
510
+ return match?.[1] || null;
511
+ }
512
+ function topLevelTomlString(text, key) {
513
+ const top = String(text || '').slice(0, Math.max(0, String(text || '').search(/^\s*\[/m)));
514
+ const match = top.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, 'm'));
515
+ return match?.[1] || null;
516
+ }
517
+ function tomlTableBlock(text, table) {
518
+ const lines = String(text || '').split(/\r?\n/);
519
+ const start = lines.findIndex((line) => tableHeaderMatches(line, table));
520
+ if (start < 0)
521
+ return null;
522
+ let end = lines.length;
523
+ for (let index = start + 1; index < lines.length; index += 1) {
524
+ if (/^\s*\[[^\]]+\]\s*$/.test(lines[index] || '')) {
525
+ end = index;
526
+ break;
527
+ }
528
+ }
529
+ return lines.slice(start, end).join('\n');
530
+ }
531
+ function removeTomlTableKeyLocal(text, table, key) {
532
+ const lines = String(text || '').split(/\r?\n/);
533
+ let inTable = false;
534
+ const out = [];
535
+ for (const line of lines) {
536
+ if (/^\s*\[[^\]]+\]\s*$/.test(line))
537
+ inTable = tableHeaderMatches(line, table);
538
+ if (inTable && new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(line))
539
+ continue;
540
+ out.push(line);
541
+ }
542
+ return out.join('\n');
543
+ }
544
+ function upsertTomlTableKeyIfAbsentLocal(text, table, line) {
545
+ const key = tomlLineKey(line);
546
+ return tomlTableHasKey(text, table, key) ? text : upsertTomlTableKeyLocal(text, table, line);
547
+ }
548
+ function upsertTomlTableKeyLocal(text, table, line) {
549
+ const raw = String(text || '').trimEnd();
550
+ const key = tomlLineKey(line);
551
+ const lines = raw ? raw.split(/\r?\n/) : [];
552
+ const start = lines.findIndex((candidate) => tableHeaderMatches(candidate, table));
553
+ if (start < 0) {
554
+ return `${raw}${raw ? '\n\n' : ''}[${table}]\n${line}\n`;
555
+ }
556
+ let end = lines.length;
557
+ for (let index = start + 1; index < lines.length; index += 1) {
558
+ if (/^\s*\[[^\]]+\]\s*$/.test(lines[index] || '')) {
559
+ end = index;
560
+ break;
561
+ }
562
+ }
563
+ for (let index = start + 1; index < end; index += 1) {
564
+ if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[index] || '')) {
565
+ if (lines[index] === line)
566
+ return `${lines.join('\n')}\n`;
567
+ lines[index] = line;
568
+ return `${lines.join('\n')}\n`;
569
+ }
570
+ }
571
+ lines.splice(end, 0, line);
572
+ return `${lines.join('\n')}\n`;
573
+ }
574
+ function upsertTopLevelTomlStringLocal(text, key, value) {
575
+ const raw = String(text || '').trimEnd();
576
+ const line = `${key} = ${JSON.stringify(value)}`;
577
+ const lines = raw ? raw.split(/\r?\n/) : [];
578
+ const firstTable = lines.findIndex((candidate) => /^\s*\[[^\]]+\]\s*$/.test(candidate || ''));
579
+ const end = firstTable < 0 ? lines.length : firstTable;
580
+ for (let index = 0; index < end; index += 1) {
581
+ if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[index] || '')) {
582
+ lines[index] = line;
583
+ return `${lines.join('\n')}\n`;
584
+ }
585
+ }
586
+ return insertTopLevelTomlKey(raw, line);
587
+ }
588
+ function tomlTableHasKey(text, table, key) {
589
+ const block = tomlTableBlock(text, table);
590
+ return Boolean(block && new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`, 'm').test(block));
591
+ }
592
+ function tableHeaderMatches(line, table) {
593
+ return new RegExp(`^\\s*\\[${escapeRegExp(table)}\\]\\s*$`).test(line || '');
594
+ }
595
+ function tomlLineKey(line) {
596
+ return String(line || '').split('=')[0]?.trim() || '';
597
+ }
598
+ function ensureTrailingNewline(text) {
599
+ return `${String(text || '').trim()}\n`;
600
+ }
601
+ function escapeRegExp(value) {
602
+ return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
603
+ }
459
604
  export async function ensureCurrentMigrationBeforeCommand(input) {
460
605
  const env = input.env || process.env;
461
606
  const command = input.command;
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '5.6.1';
1
+ export const PACKAGE_VERSION = '5.7.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { codexFastModeDesktopStatus, codexLbConfigPath, configureCodexLb, ensureGlobalCodexFastModeDuringInstall, releaseCodexLbAuthHold, repairCodexLbAuth } from '../cli/install-helpers.js';
6
6
  import { repairCodexConfigStructure, splitCodexProjectConfigPolicy } from '../core/codex/codex-project-config-policy.js';
7
7
  import { parseCodexConfigToml, validateCodexConfigRoundTrip } from '../core/codex/codex-config-toml.js';
8
+ import { REQUIRED_CODEX_MODEL } from '../core/codex-model-guard.js';
8
9
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-lb-gpt55-fast-'));
9
10
  const home = path.join(tmp, 'home');
10
11
  const root = path.join(tmp, 'project');
@@ -44,7 +45,7 @@ await fs.writeFile(projectConfig, [
44
45
  'enabled = true',
45
46
  '',
46
47
  '[profiles.sks-fast-high]',
47
- 'model = "gpt-5.5"',
48
+ `model = "${REQUIRED_CODEX_MODEL}"`,
48
49
  'service_tier = "fast"',
49
50
  ''
50
51
  ].join('\n'));
@@ -85,7 +86,7 @@ function assertFastProfile(text, label) {
85
86
  ...validation.blockers,
86
87
  ...(parsed.default_profile === 'sks-fast-high' ? [] : ['default_profile_not_sks_fast_high']),
87
88
  ...(parsed.user?.fast_mode?.default_profile === undefined ? [] : ['default_profile_inside_user_fast_mode']),
88
- ...(profile.model === 'gpt-5.5' ? [] : ['sks_fast_high_model_not_gpt55']),
89
+ ...(profile.model === REQUIRED_CODEX_MODEL ? [] : ['sks_fast_high_model_not_required_codex_model']),
89
90
  ...(profile.service_tier === 'fast' ? [] : ['sks_fast_high_service_tier_not_fast']),
90
91
  ...(provider.requires_openai_auth === true ? [] : ['codex_lb_requires_openai_auth_not_true']),
91
92
  ...(provider.wire_api === 'responses' ? [] : ['codex_lb_wire_api_not_responses']),
@@ -12,8 +12,10 @@ const MAX_UNPACKED = Number(process.env.SKS_MAX_UNPACKED_BYTES || 10 * 1024 * 10
12
12
  // (route-success-helpers.ts, seo-command.ts, ppt-command.ts, qa-loop-command.ts,
13
13
  // research-command.ts, image-ux-review-command.ts, mad-sks-command.ts,
14
14
  // sks-menubar.ts) with genuine new production logic, pushing the packed size
15
- // to ~2306 KiB. Kept modest headroom rather than a large jump.
16
- const MAX_PACKED = Number(process.env.SKS_MAX_PACK_BYTES || 2340 * 1024);
15
+ // to ~2306 KiB. 5.7.0 adds doctor/update migration repair coverage and publish
16
+ // contract checks, pushing the packed tarball to ~2345 KiB. Keep a narrow cap
17
+ // rather than giving the package a broad size budget.
18
+ const MAX_PACKED = Number(process.env.SKS_MAX_PACK_BYTES || 2350 * 1024);
17
19
  function runNpmPack() {
18
20
  const npmCli = process.env.npm_execpath; // set when invoked via `npm run`
19
21
  const npmCache = process.env.SKS_RELEASE_NPM_CACHE || path.join(os.tmpdir(), 'sneakoscope-npm-cache');
@@ -393,7 +393,18 @@ for (const gate of allManifestGates)
393
393
  assertGate(pkg.bin?.sks === 'dist/bin/sks.js', 'package runtime must use dist/bin/sks.js');
394
394
  assertGate(pkg.bin?.sneakoscope === 'dist/bin/sks.js', 'sneakoscope runtime must use dist/bin/sks.js');
395
395
  assertGate(!pkg.files?.includes('src'), 'package files must not include src runtime shadows');
396
- assertGate(pkg.scripts?.['publish:prep-ignore-scripts'] === 'npm run prepublishOnly', 'publish:prep-ignore-scripts must run the prepublishOnly release gate before lifecycle-disabled publish', { script: pkg.scripts?.['publish:prep-ignore-scripts'] || null });
396
+ const publishPrepIgnoreScripts = String(pkg.scripts?.['publish:prep-ignore-scripts'] || '');
397
+ assertGate(!/\bprepublishOnly\b/.test(publishPrepIgnoreScripts), 'publish:prep-ignore-scripts must not depend on npm lifecycle hooks that --ignore-scripts disables', { script: publishPrepIgnoreScripts || null });
398
+ for (const required of [
399
+ 'build:incremental',
400
+ 'release:version-truth',
401
+ 'publish:packlist-performance',
402
+ 'package-published-contract-check.js',
403
+ 'release-registry-check.js --require-unpublished --require-publish-auth',
404
+ 'publish-tag:check'
405
+ ]) {
406
+ assertGate(publishPrepIgnoreScripts.includes(required), `publish:prep-ignore-scripts missing lifecycle-disabled publish preflight: ${required}`, { script: publishPrepIgnoreScripts || null });
407
+ }
397
408
  assertGate(String(pkg.scripts?.['publish:ignore-scripts'] || '').includes('npm run publish:prep-ignore-scripts'), 'publish:ignore-scripts must run publish:prep-ignore-scripts before npm publish --ignore-scripts', { script: pkg.scripts?.['publish:ignore-scripts'] || null });
398
409
  assertGate(String(pkg.scripts?.['publish:ignore-scripts'] || '').includes('--ignore-scripts'), 'publish:ignore-scripts must keep npm lifecycle scripts disabled for the final publish', { script: pkg.scripts?.['publish:ignore-scripts'] || null });
399
410
  for (const file of requiredDocs) {