sneakoscope 0.9.6 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.9.6",
4
+ "version": "0.9.7",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -95,6 +95,17 @@ async function reportPostinstallCodexLbAuth() {
95
95
  } else if (reconcile?.status === 'failed') {
96
96
  console.log(`codex-lb auth: ChatGPT OAuth reconciliation could not complete (${reconcile.reason || 'unknown'}${reconcile.error ? `: ${reconcile.error}` : ''}). Run \`sks codex-lb repair\` to retry.`);
97
97
  }
98
+ if (codexLbAuth.base_url && codexLbAuth.codex_lb?.env_key_configured && canAskYesNo() && process.env.SKS_SKIP_CODEX_LB_KEY_PROMPT !== '1') {
99
+ const changeKey = (await askPostinstallQuestion('codex-lb key changed? Update now? [y/N] ')).trim();
100
+ if (/^(y|yes|예|네|응)$/i.test(changeKey)) {
101
+ const newKey = (await askPostinstallQuestion('New codex-lb API key (sk-clb-...): ')).trim();
102
+ if (newKey) {
103
+ const result = await configureCodexLb({ host: codexLbAuth.base_url, apiKey: newKey });
104
+ if (result.ok) console.log(`codex-lb key updated: ${result.base_url}`);
105
+ else console.log(`codex-lb key update failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
106
+ }
107
+ }
108
+ }
98
109
  return codexLbAuth;
99
110
  }
100
111
 
@@ -457,6 +468,7 @@ export async function repairCodexLbAuth(opts = {}) {
457
468
  codex_lb: status
458
469
  };
459
470
  }
471
+ await migrateCodexAuthKeyFormat({ home: opts.home });
460
472
  const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
461
473
  const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
462
474
  const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
@@ -482,6 +494,7 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
482
494
  if (process.env.SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH=1' };
483
495
  const status = await codexLbStatus(opts);
484
496
  if (!status.selected && !status.provider_configured && !status.env_file) return { status: 'not_configured', codex_lb: status };
497
+ await migrateCodexAuthKeyFormat({ home: opts.home });
485
498
  if (status.ok && (!status.selected || !status.provider_requires_openai_auth)) return repairCodexLbAuth(opts);
486
499
  if (!status.ok) {
487
500
  if (status.base_url && (status.env_key_configured || status.provider_configured || status.selected || status.env_base_url_configured)) return repairCodexLbAuth(opts);
@@ -552,6 +565,28 @@ function parseCodexAuthApiKey(text = '') {
552
565
  }
553
566
  }
554
567
 
568
+ // Migrate auth.json from legacy {"auth_mode":"apikey","key":"..."} to the codex 0.130.0+
569
+ // format {"auth_mode":"apikey","OPENAI_API_KEY":"..."}. Safe: preserves key value, only renames field.
570
+ async function migrateCodexAuthKeyFormat(opts = {}) {
571
+ const home = opts.home || process.env.HOME || os.homedir();
572
+ const authPath = opts.authPath || codexAuthPath(home);
573
+ const text = await readText(authPath, '');
574
+ if (!text.trim()) return { status: 'skipped', reason: 'empty' };
575
+ try {
576
+ const parsed = JSON.parse(text);
577
+ if (parsed?.auth_mode !== 'apikey') return { status: 'skipped', reason: 'not_apikey' };
578
+ if (parsed.OPENAI_API_KEY) return { status: 'skipped', reason: 'already_migrated' };
579
+ const legacyKey = parsed.key || parsed.api_key || parsed.apiKey || parsed.openai_api_key;
580
+ if (!legacyKey) return { status: 'skipped', reason: 'no_key_found' };
581
+ const replacement = `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: legacyKey })}\n`;
582
+ await writeTextAtomic(authPath, replacement);
583
+ await fsp.chmod(authPath, 0o600).catch(() => {});
584
+ return { status: 'migrated', auth_path: authPath };
585
+ } catch {
586
+ return { status: 'skipped', reason: 'parse_error' };
587
+ }
588
+ }
589
+
555
590
  // When codex-lb is selected with env_key auth AND auth.json also carries a real ChatGPT OAuth
556
591
  // token blob, Codex CLI/App can pick the OAuth bearer over the env_key bearer and fail against
557
592
  // the load balancer. We back the OAuth blob up to ~/.codex/auth.chatgpt-backup.json and replace
@@ -597,7 +632,7 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
597
632
  };
598
633
  }
599
634
  try {
600
- const replacement = `${JSON.stringify({ auth_mode: 'apikey', key: apiKey }, null, 2)}\n`;
635
+ const replacement = `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: apiKey }, null, 2)}\n`;
601
636
  await writeTextAtomic(authPath, replacement);
602
637
  await fsp.chmod(authPath, 0o600).catch(() => {});
603
638
  } catch (err) {
@@ -1567,7 +1602,7 @@ export async function selftestCodexLb(tmp) {
1567
1602
  // NOTE: printf format uses literal double-quotes inside single-quoted shell strings so the
1568
1603
  // fake login writes proper JSON in both bash and dash (where `\"` is a non-standard printf
1569
1604
  // escape that dash emits literally and bash collapses to `"`).
1570
- await writeTextAtomic(codexLbFakeCodex, "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo \"codex-cli 99.0.0\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"status\" ]; then echo \"logged in with browser auth\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"--with-api-key\" ]; then read key; mkdir -p \"$HOME/.codex\"; printf '{\"auth_mode\":\"apikey\",\"key\":\"%s\"}\\n' \"$key\" > \"$HOME/.codex/auth.json\"; printf '%s\\n' \"$key\" >> \"$HOME/.codex/login-calls.log\"; exit 0; fi\necho \"fake codex unsupported\" >&2\nexit 1\n");
1605
+ await writeTextAtomic(codexLbFakeCodex, "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo \"codex-cli 99.0.0\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"status\" ]; then echo \"logged in with browser auth\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"--with-api-key\" ]; then read key; mkdir -p \"$HOME/.codex\"; printf '{\"auth_mode\":\"apikey\",\"OPENAI_API_KEY\":\"%s\"}\\n' \"$key\" > \"$HOME/.codex/auth.json\"; printf '%s\\n' \"$key\" >> \"$HOME/.codex/login-calls.log\"; exit 0; fi\necho \"fake codex unsupported\" >&2\nexit 1\n");
1571
1606
  await fsp.chmod(codexLbFakeCodex, 0o755);
1572
1607
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "low"\nservice_tier = "fast"\n\n[profiles.custom]\nmodel_reasoning_effort = "low"\n\n[notice]\nfast_default_opt_out = true\n\n[features]\ncodex_hooks = true\n');
1573
1608
  const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}`, SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1' };
@@ -1710,7 +1745,7 @@ export async function selftestCodexLb(tmp) {
1710
1745
  // the provider stays selected and whether the backup file is removed after restore.
1711
1746
  const codexLbReleaseConfig = 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = true\n';
1712
1747
  const codexLbReleaseEnv = "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-test'\n";
1713
- const codexLbReleaseApikeyAuth = '{"auth_mode":"apikey","key":"sk-test"}\n';
1748
+ const codexLbReleaseApikeyAuth = '{"auth_mode":"apikey","OPENAI_API_KEY":"sk-test"}\n';
1714
1749
  const codexLbReleaseOauthBackup = `${oauthAuthJson}\n`;
1715
1750
  // Happy path: deselect model_provider and preserve backup file.
1716
1751
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), codexLbReleaseApikeyAuth);
package/src/cli/main.mjs CHANGED
@@ -1248,13 +1248,20 @@ async function codexLbCommand(action = 'status', args = []) {
1248
1248
  return;
1249
1249
  }
1250
1250
  if (sub === 'setup' || sub === 'reconfigure') {
1251
- const host = readOption(args, '--host', readOption(args, '--domain', null));
1252
- const apiKey = readOption(args, '--api-key', readOption(args, '--key', null));
1251
+ let host = readOption(args, '--host', readOption(args, '--domain', null));
1252
+ let apiKey = readOption(args, '--api-key', readOption(args, '--key', null));
1253
1253
  if (!host || !apiKey) {
1254
1254
  if (json) return console.log(JSON.stringify({ ok: false, reason: 'missing_host_or_api_key' }, null, 2));
1255
- console.error('Usage: sks codex-lb setup|reconfigure --host <domain> --api-key <key>');
1256
- process.exitCode = 1;
1257
- return;
1255
+ if (!canAskYesNo()) {
1256
+ console.error('Usage: sks codex-lb setup|reconfigure --host <domain> --api-key <key>');
1257
+ process.exitCode = 1;
1258
+ return;
1259
+ }
1260
+ console.log('codex-lb setup — configure your Codex load balancer connection.\n');
1261
+ if (!host) host = (await askPostinstallQuestion('Your codex-lb domain (e.g. https://codex.example.com/backend-api/codex): ')).trim();
1262
+ if (!host) { console.error('Setup cancelled: no domain provided.'); process.exitCode = 1; return; }
1263
+ if (!apiKey) apiKey = (await askPostinstallQuestion('Your codex-lb API key (sk-clb-...): ')).trim();
1264
+ if (!apiKey) { console.error('Setup cancelled: no API key provided.'); process.exitCode = 1; return; }
1258
1265
  }
1259
1266
  const result = await configureCodexLb({ host, apiKey });
1260
1267
  if (json) return console.log(JSON.stringify(result, null, 2));
package/src/core/fsx.mjs 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
 
8
- export const PACKAGE_VERSION = '0.9.6';
8
+ export const PACKAGE_VERSION = '0.9.7';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11