sneakoscope 0.8.4 → 0.8.5
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/README.md +7 -3
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +248 -55
- package/src/cli/main.mjs +4 -4
- package/src/core/fsx.mjs +1 -1
- package/src/core/routes.mjs +2 -2
package/README.md
CHANGED
|
@@ -174,9 +174,9 @@ sks codex-lb repair
|
|
|
174
174
|
sks
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, syncs `codex login
|
|
177
|
+
Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, loads the provider env key for tmux launches, and syncs the macOS user launch environment so the Codex App can see `CODEX_LB_API_KEY` after restart. npm postinstall upgrades resync that stored env file when postinstall is not stopped by a hard harness conflict, and `sks doctor --fix` does the same during repair. If an older SKS release left the codex-lb dashboard key only in the shared Codex `auth.json` login cache, SKS migrates that key back into `~/.codex/sks-codex-lb.env` when a codex-lb provider or env base URL is already recoverable. It does not rewrite the shared Codex `auth.json` login cache by default; set `SKS_CODEX_LB_SYNC_CODEX_LOGIN=1` only if you intentionally want the old API-key login-cache behavior. When codex-lb is active, SKS opens a fresh `sks-codex-lb-*` tmux session and sweeps older detached codex-lb sessions for the same repo before launch so stale Responses API chains are not reused. Configured launch paths, including non-interactive runs, verify that codex-lb can continue a Responses API chain with `previous_response_id`; if that check fails, SKS bypasses codex-lb for that launch with `model_provider="openai"` instead of letting the Codex session fail mid-work.
|
|
178
178
|
|
|
179
|
-
If
|
|
179
|
+
If codex-lb provider auth drifts after launch/reinstall, run `sks doctor --fix` or `sks codex-lb repair`; to replace it, run `sks codex-lb reconfigure --host <domain> --api-key <key>`.
|
|
180
180
|
|
|
181
181
|
### MAD tmux Launch
|
|
182
182
|
|
|
@@ -185,7 +185,7 @@ sks --mad
|
|
|
185
185
|
sks --mad --yes
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
This syncs existing codex-lb
|
|
188
|
+
This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` full-access profile, opens the MAD-SKS permission gate for that tmux run, and launches a single Codex CLI pane. The session recreates the named session so stale split-pane MAD sessions collapse back to one pane. Catastrophic database wipe/all-row/project-management safeguards remain active, and the pipeline contract still forbids unrequested fallback implementation code.
|
|
189
189
|
|
|
190
190
|
Before launching, SKS checks npm for a newer `sneakoscope`; answer `y` to update or `n` to continue. Use `--yes` to approve missing dependency installs automatically.
|
|
191
191
|
|
|
@@ -407,6 +407,10 @@ codex mcp list
|
|
|
407
407
|
|
|
408
408
|
Codex App workflows need the app installed. UI/browser evidence requires first-party Codex Computer Use, and generated raster/image-review evidence requires real `$imagegen`/`gpt-image-2` output. After setup/upgrade, start a fresh thread so Codex reloads plugin tools.
|
|
409
409
|
|
|
410
|
+
### Codex App UI looks stale after codex-lb changes
|
|
411
|
+
|
|
412
|
+
If Codex App UI panels or auth-dependent controls still look wrong after codex-lb setup, repair, or upgrade, restart the app first. If the UI still does not recover, sign out of Codex App, sign back in, then run `sks codex-app check` or `sks codex-lb repair` as needed.
|
|
413
|
+
|
|
410
414
|
### Setup is blocked by another harness
|
|
411
415
|
|
|
412
416
|
```sh
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.5",
|
|
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",
|
|
@@ -81,7 +81,8 @@ export async function postinstall({ bootstrap }) {
|
|
|
81
81
|
|
|
82
82
|
async function reportPostinstallCodexLbAuth() {
|
|
83
83
|
const codexLbAuth = await ensureCodexLbAuthDuringInstall();
|
|
84
|
-
if (codexLbAuth.
|
|
84
|
+
if (codexLbAuth.legacy_auth_migrated) console.log(`codex-lb auth: restored from existing Codex login cache into ${codexLbAuth.env_path}.`);
|
|
85
|
+
else if (codexLbAuth.status === 'synced' || codexLbAuth.status === 'present' || codexLbAuth.status === 'repaired') console.log(`codex-lb auth: preserved from ${codexLbAuth.env_path}.`);
|
|
85
86
|
else if (codexLbAuth.status === 'skipped') console.log(`codex-lb auth: skipped (${codexLbAuth.reason}).`);
|
|
86
87
|
else if (codexLbAuth.status === 'missing_env_key') console.log('codex-lb auth: stored key missing. Run `sks codex-lb setup --host <domain> --api-key <key>` to repair.');
|
|
87
88
|
else if (codexLbAuth.status === 'missing_base_url') console.log('codex-lb auth: stored key has no recoverable base URL. Run `sks codex-lb reconfigure --host <domain> --api-key <key>` once.');
|
|
@@ -155,6 +156,10 @@ export function codexLbEnvPath(home = process.env.HOME || os.homedir()) {
|
|
|
155
156
|
return path.join(home, '.codex', 'sks-codex-lb.env');
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
function codexAuthPath(home = process.env.HOME || os.homedir()) {
|
|
160
|
+
return path.join(home, '.codex', 'auth.json');
|
|
161
|
+
}
|
|
162
|
+
|
|
158
163
|
async function capturePostinstallCodexLbConfigSnapshot(home = process.env.HOME || os.homedir()) {
|
|
159
164
|
const configPath = codexLbConfigPath(home);
|
|
160
165
|
const envPath = codexLbEnvPath(home);
|
|
@@ -198,9 +203,20 @@ export async function configureCodexLb(opts = {}) {
|
|
|
198
203
|
await writeTextAtomic(configPath, next);
|
|
199
204
|
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
200
205
|
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
201
|
-
|
|
202
|
-
const codexLogin = await
|
|
203
|
-
|
|
206
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment({ env_path: envPath, base_url: baseUrl }, { ...opts, home });
|
|
207
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home, force: true });
|
|
208
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
209
|
+
return {
|
|
210
|
+
ok,
|
|
211
|
+
status: ok ? 'configured' : (codexEnvironment.status || codexLogin.status),
|
|
212
|
+
config_path: configPath,
|
|
213
|
+
env_path: envPath,
|
|
214
|
+
base_url: baseUrl,
|
|
215
|
+
env_key: 'CODEX_LB_API_KEY',
|
|
216
|
+
codex_environment: codexEnvironment,
|
|
217
|
+
codex_login: codexLogin,
|
|
218
|
+
error: codexEnvironment.error || codexLogin.error || null
|
|
219
|
+
};
|
|
204
220
|
}
|
|
205
221
|
|
|
206
222
|
export async function codexLbStatus(opts = {}) {
|
|
@@ -360,8 +376,18 @@ function codexLbProviderBaseUrl(text = '') {
|
|
|
360
376
|
export async function repairCodexLbAuth(opts = {}) {
|
|
361
377
|
let status = await codexLbStatus(opts);
|
|
362
378
|
let configRepaired = false;
|
|
379
|
+
let legacyAuthMigrated = false;
|
|
380
|
+
let legacyAuthPath = null;
|
|
363
381
|
const currentConfig = await readText(status.config_path, '');
|
|
364
|
-
if (status.env_key_configured && status.base_url && (
|
|
382
|
+
if (!status.env_key_configured && status.base_url && (status.provider_configured || status.selected || status.env_base_url_configured)) {
|
|
383
|
+
const legacyAuth = await restoreCodexLbEnvFromSharedLogin(status, opts);
|
|
384
|
+
if (legacyAuth.ok) {
|
|
385
|
+
legacyAuthMigrated = true;
|
|
386
|
+
legacyAuthPath = legacyAuth.auth_path;
|
|
387
|
+
status = await codexLbStatus(opts);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (status.env_key_configured && status.base_url && (!status.ok || status.selected || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig))) {
|
|
365
391
|
await ensureDir(path.dirname(status.config_path));
|
|
366
392
|
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(currentConfig, status.base_url));
|
|
367
393
|
await writeTextAtomic(status.config_path, next);
|
|
@@ -377,15 +403,21 @@ export async function repairCodexLbAuth(opts = {}) {
|
|
|
377
403
|
codex_lb: status
|
|
378
404
|
};
|
|
379
405
|
}
|
|
380
|
-
const
|
|
406
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
407
|
+
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
408
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
|
|
409
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
381
410
|
return {
|
|
382
|
-
ok
|
|
383
|
-
status:
|
|
411
|
+
ok,
|
|
412
|
+
status: ok ? 'repaired' : (codexEnvironment.status || codexLogin.status),
|
|
384
413
|
config_path: status.config_path,
|
|
385
414
|
env_path: status.env_path,
|
|
386
415
|
base_url: status.base_url,
|
|
387
416
|
config_repaired: configRepaired,
|
|
417
|
+
legacy_auth_migrated: legacyAuthMigrated,
|
|
418
|
+
legacy_auth_path: legacyAuthPath,
|
|
388
419
|
codex_lb: status,
|
|
420
|
+
codex_environment: codexEnvironment,
|
|
389
421
|
codex_login: codexLogin
|
|
390
422
|
};
|
|
391
423
|
}
|
|
@@ -395,34 +427,56 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
|
395
427
|
const status = await codexLbStatus(opts);
|
|
396
428
|
if (!status.selected && !status.provider_configured && !status.env_file) return { status: 'not_configured', codex_lb: status };
|
|
397
429
|
if (!status.ok) {
|
|
398
|
-
if (status.
|
|
430
|
+
if (status.base_url && (status.env_key_configured || status.provider_configured || status.selected || status.env_base_url_configured)) return repairCodexLbAuth(opts);
|
|
399
431
|
return { status: status.env_key_configured ? 'missing_base_url' : 'missing_env_key', codex_lb: status, config_path: status.config_path, env_path: status.env_path };
|
|
400
432
|
}
|
|
401
|
-
const
|
|
433
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
434
|
+
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
435
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
|
|
436
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
402
437
|
return {
|
|
403
|
-
ok
|
|
404
|
-
status: codexLogin.status,
|
|
438
|
+
ok,
|
|
439
|
+
status: ok ? 'present' : (codexEnvironment.status || codexLogin.status),
|
|
405
440
|
config_path: status.config_path,
|
|
406
441
|
env_path: status.env_path,
|
|
407
442
|
base_url: status.base_url,
|
|
408
443
|
codex_lb: status,
|
|
444
|
+
codex_environment: codexEnvironment,
|
|
409
445
|
codex_login: codexLogin,
|
|
410
|
-
error: codexLogin.error || null
|
|
446
|
+
error: codexEnvironment.error || codexLogin.error || null
|
|
411
447
|
};
|
|
412
448
|
}
|
|
413
449
|
|
|
450
|
+
async function restoreCodexLbEnvFromSharedLogin(status = {}, opts = {}) {
|
|
451
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
452
|
+
const authPath = opts.authPath || codexAuthPath(home);
|
|
453
|
+
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
454
|
+
const authText = await readText(authPath, '');
|
|
455
|
+
const apiKey = parseCodexSharedLoginApiKey(authText);
|
|
456
|
+
if (!apiKey) return { ok: false, status: 'missing_legacy_login_key', auth_path: authPath, env_path: envPath };
|
|
457
|
+
const baseUrl = status.base_url || parseCodexLbEnvBaseUrl(await readText(envPath, ''));
|
|
458
|
+
if (!baseUrl) return { ok: false, status: 'missing_base_url', auth_path: authPath, env_path: envPath };
|
|
459
|
+
await ensureDir(path.dirname(envPath));
|
|
460
|
+
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(normalizeCodexLbBaseUrl(baseUrl))}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
461
|
+
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
462
|
+
return { ok: true, status: 'migrated_login_cache', auth_path: authPath, env_path: envPath, base_url: normalizeCodexLbBaseUrl(baseUrl) };
|
|
463
|
+
}
|
|
464
|
+
|
|
414
465
|
export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
415
466
|
if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1') return { status: 'skipped' };
|
|
416
467
|
const status = await codexLbStatus(opts);
|
|
417
468
|
if (status.ok) {
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
469
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
470
|
+
if (codexEnvironment.status === 'synced') console.log('codex-lb provider auth synced for this user session.');
|
|
471
|
+
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
472
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir() });
|
|
473
|
+
if (codexLogin.status === 'synced') console.log('codex-lb auth synced with Codex CLI login cache.');
|
|
420
474
|
const chainHealth = await checkCodexLbResponseChain(status, opts);
|
|
421
475
|
if (!chainHealth.ok && chainHealth.chain_unhealthy) {
|
|
422
476
|
console.log(`codex-lb response chain check failed (${chainHealth.status}); bypassing codex-lb for this launch.`);
|
|
423
|
-
return { status: 'chain_unhealthy', ...status, ok: false, codex_login: codexLogin, chain_health: chainHealth, bypass_codex_lb: true };
|
|
477
|
+
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth, bypass_codex_lb: true };
|
|
424
478
|
}
|
|
425
|
-
return { status: 'present', ...status, codex_login: codexLogin, chain_health: chainHealth };
|
|
479
|
+
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth };
|
|
426
480
|
}
|
|
427
481
|
if (!canAskYesNo()) return { status: 'non_interactive', codex_lb: status };
|
|
428
482
|
const useCodexLb = (await askPostinstallQuestion('\nAuthenticate and route Codex through codex-lb? [y/N] ')).trim();
|
|
@@ -435,12 +489,57 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
435
489
|
return configured;
|
|
436
490
|
}
|
|
437
491
|
|
|
438
|
-
async function
|
|
492
|
+
async function syncCodexLbProviderEnvironment(status = {}, opts = {}) {
|
|
439
493
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
440
494
|
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
441
|
-
const
|
|
495
|
+
const envText = await readText(envPath, '');
|
|
496
|
+
const apiKey = parseCodexLbEnvKey(envText);
|
|
497
|
+
if (!apiKey) return { ok: false, status: 'missing_env_key' };
|
|
498
|
+
const baseUrl = status.base_url || parseCodexLbEnvBaseUrl(envText);
|
|
499
|
+
process.env.CODEX_LB_API_KEY = apiKey;
|
|
500
|
+
if (baseUrl) process.env.CODEX_LB_BASE_URL = baseUrl;
|
|
501
|
+
const launchEnv = await syncCodexLbMacLaunchEnvironment({ CODEX_LB_API_KEY: apiKey, ...(baseUrl ? { CODEX_LB_BASE_URL: baseUrl } : {}) }, opts);
|
|
502
|
+
const ok = launchEnv.ok || launchEnv.skipped || launchEnv.status === 'not_macos';
|
|
503
|
+
return {
|
|
504
|
+
ok,
|
|
505
|
+
status: launchEnv.status === 'synced' ? 'synced' : ok ? 'process_env' : launchEnv.status,
|
|
506
|
+
env_path: envPath,
|
|
507
|
+
base_url: baseUrl || null,
|
|
508
|
+
launch_environment: launchEnv,
|
|
509
|
+
error: launchEnv.error || null
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
|
|
514
|
+
if (opts.syncLaunchEnv === false || process.env.SKS_SKIP_CODEX_LB_LAUNCH_ENV === '1') return { ok: true, status: 'skipped', skipped: true, reason: 'SKS_SKIP_CODEX_LB_LAUNCH_ENV=1' };
|
|
515
|
+
if (process.platform !== 'darwin' && !opts.forceLaunchEnv) return { ok: true, status: 'not_macos', skipped: true };
|
|
516
|
+
const launchctl = opts.launchctlBin || await which('launchctl').catch(() => null) || await exists('/bin/launchctl').then((ok) => ok ? '/bin/launchctl' : null).catch(() => null);
|
|
517
|
+
if (!launchctl) return { ok: false, status: 'launchctl_missing', error: 'launchctl not found on PATH' };
|
|
518
|
+
const variables = Object.entries(values).filter(([, value]) => value);
|
|
519
|
+
const results = [];
|
|
520
|
+
for (const [key, value] of variables) {
|
|
521
|
+
const result = await runProcess(launchctl, ['setenv', key, value], { timeoutMs: 5000, maxOutputBytes: 8192 });
|
|
522
|
+
results.push({
|
|
523
|
+
key,
|
|
524
|
+
ok: result.code === 0,
|
|
525
|
+
error: result.code === 0 ? null : redactSecretText(result.stderr || result.stdout || 'launchctl setenv failed', [value]).trim()
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
const failed = results.filter((result) => !result.ok);
|
|
529
|
+
if (failed.length) return { ok: false, status: 'launch_env_failed', variables: results.map((result) => result.key), failed, error: failed.map((result) => `${result.key}: ${result.error}`).join('; ') };
|
|
530
|
+
return { ok: true, status: 'synced', variables: results.map((result) => result.key) };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function maybeSyncCodexLbSharedLogin(apiKey, opts = {}) {
|
|
442
534
|
if (!apiKey) return { ok: false, status: 'missing_env_key' };
|
|
443
|
-
|
|
535
|
+
if (!shouldSyncCodexLbSharedLogin(opts)) {
|
|
536
|
+
return { ok: true, status: 'skipped', reason: 'codex-lb uses provider env_key auth; set SKS_CODEX_LB_SYNC_CODEX_LOGIN=1 to also rewrite Codex shared login cache.' };
|
|
537
|
+
}
|
|
538
|
+
return syncCodexApiKeyLogin(apiKey, opts);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function shouldSyncCodexLbSharedLogin(opts = {}) {
|
|
542
|
+
return opts.syncCodexLogin === true || process.env.SKS_CODEX_LB_SYNC_CODEX_LOGIN === '1';
|
|
444
543
|
}
|
|
445
544
|
|
|
446
545
|
async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
@@ -663,6 +762,19 @@ function parseCodexLbEnvBaseUrl(text = '') {
|
|
|
663
762
|
return value ? normalizeCodexLbBaseUrl(value) : '';
|
|
664
763
|
}
|
|
665
764
|
|
|
765
|
+
function parseCodexSharedLoginApiKey(text = '') {
|
|
766
|
+
try {
|
|
767
|
+
const parsed = JSON.parse(String(text || ''));
|
|
768
|
+
const authMode = String(parsed?.auth_mode || parsed?.authMode || parsed?.mode || '').toLowerCase();
|
|
769
|
+
const key = parsed?.key || parsed?.api_key || parsed?.apiKey || parsed?.openai_api_key || parsed?.OPENAI_API_KEY;
|
|
770
|
+
if (!key || typeof key !== 'string') return '';
|
|
771
|
+
if (authMode && !/api[-_]?key|apikey/.test(authMode)) return '';
|
|
772
|
+
return key.trim();
|
|
773
|
+
} catch {
|
|
774
|
+
return '';
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
666
778
|
function parseShellEnvValue(text = '', key = '') {
|
|
667
779
|
const re = new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=\\s*(.+?)\\s*$`, 'm');
|
|
668
780
|
const envMatch = String(text || '').match(re);
|
|
@@ -1082,6 +1194,24 @@ async function safeReadText(file, fallback = '') {
|
|
|
1082
1194
|
}
|
|
1083
1195
|
}
|
|
1084
1196
|
|
|
1197
|
+
async function codexLbLoginCallCount(home) {
|
|
1198
|
+
return (await safeReadText(path.join(home, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function codexLbPostinstallEnv(baseEnv, overrides = {}) {
|
|
1202
|
+
return {
|
|
1203
|
+
...baseEnv,
|
|
1204
|
+
SKS_POSTINSTALL_NO_BOOTSTRAP: '1',
|
|
1205
|
+
SKS_SKIP_POSTINSTALL_SHIM: '1',
|
|
1206
|
+
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1207
|
+
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1208
|
+
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1209
|
+
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1210
|
+
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1',
|
|
1211
|
+
...overrides
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1085
1215
|
export async function selftestCodexLb(tmp) {
|
|
1086
1216
|
const codexLbHome = path.join(tmp, 'codex-lb-home');
|
|
1087
1217
|
await ensureDir(path.join(codexLbHome, '.codex'));
|
|
@@ -1091,7 +1221,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1091
1221
|
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");
|
|
1092
1222
|
await fsp.chmod(codexLbFakeCodex, 0o755);
|
|
1093
1223
|
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');
|
|
1094
|
-
const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}
|
|
1224
|
+
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' };
|
|
1095
1225
|
const codexLbSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'setup', '--host', 'lb.example.test', '--api-key', 'sk-test', '--json'], {
|
|
1096
1226
|
cwd: tmp,
|
|
1097
1227
|
env: codexLbEnvForSelftest,
|
|
@@ -1103,7 +1233,12 @@ export async function selftestCodexLb(tmp) {
|
|
|
1103
1233
|
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1104
1234
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1105
1235
|
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1106
|
-
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || hasTopLevelCodexLbSelected(codexLbConfig) || !codexLbConfig.includes('[model_providers.codex-lb]') || !codexLbEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbEnv.includes("CODEX_LB_API_KEY='sk-test'") ||
|
|
1236
|
+
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || hasTopLevelCodexLbSelected(codexLbConfig) || !codexLbConfig.includes('[model_providers.codex-lb]') || !codexLbEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbEnv.includes("CODEX_LB_API_KEY='sk-test'") || codexLbSetupJson.codex_environment?.ok !== true || codexLbSetupJson.codex_login?.status !== 'skipped' || codexLbAuth.trim()) throw new Error('selftest: codex-lb setup');
|
|
1237
|
+
const codexLbFailLaunchctl = path.join(codexLbFakeBin, 'launchctl-fail');
|
|
1238
|
+
await writeTextAtomic(codexLbFailLaunchctl, '#!/bin/sh\necho "launchctl denied" >&2\nexit 7\n');
|
|
1239
|
+
await fsp.chmod(codexLbFailLaunchctl, 0o755);
|
|
1240
|
+
const codexLbFailedLaunchEnv = await configureCodexLb({ home: path.join(tmp, 'codex-lb-launch-fail-home'), host: 'lb.example.test', apiKey: 'sk-fail', forceLaunchEnv: true, launchctlBin: codexLbFailLaunchctl });
|
|
1241
|
+
if (codexLbFailedLaunchEnv.ok || codexLbFailedLaunchEnv.status !== 'launch_env_failed' || !/launchctl denied/.test(codexLbFailedLaunchEnv.error || '')) throw new Error('selftest: codex-lb setup must expose launch-env failure');
|
|
1107
1242
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
1108
1243
|
await initProject(codexLbHome, { installScope: 'global', force: true, repair: true });
|
|
1109
1244
|
const codexLbRepairSetupConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
@@ -1121,30 +1256,27 @@ export async function selftestCodexLb(tmp) {
|
|
|
1121
1256
|
if (codexLbRepair.code !== 0) throw new Error(`selftest: codex-lb repair exited ${codexLbRepair.code}: ${codexLbRepair.stderr}`);
|
|
1122
1257
|
const codexLbRepairJson = JSON.parse(codexLbRepair.stdout);
|
|
1123
1258
|
const codexLbRepairedAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1124
|
-
if (!codexLbRepairJson.ok || codexLbRepairJson.status !== 'repaired' || !codexLbRepairedAuth.includes('"auth_mode":"
|
|
1125
|
-
const
|
|
1259
|
+
if (!codexLbRepairJson.ok || codexLbRepairJson.status !== 'repaired' || codexLbRepairJson.codex_environment?.ok !== true || codexLbRepairJson.codex_login?.status !== 'skipped' || !codexLbRepairedAuth.includes('"auth_mode":"browser"') || codexLbRepairedAuth.includes('sk-test')) throw new Error('selftest: codex-lb repair');
|
|
1260
|
+
const codexLbLegacyRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'auth', 'repair', '--json'], { cwd: tmp, env: { ...codexLbEnvForSelftest, SKS_CODEX_LB_SYNC_CODEX_LOGIN: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
1261
|
+
if (codexLbLegacyRepair.code !== 0) throw new Error(`selftest: codex-lb legacy login repair exited ${codexLbLegacyRepair.code}: ${codexLbLegacyRepair.stderr}`);
|
|
1262
|
+
const codexLbLegacyRepairJson = JSON.parse(codexLbLegacyRepair.stdout);
|
|
1263
|
+
const codexLbLegacyAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1264
|
+
if (!codexLbLegacyRepairJson.ok || codexLbLegacyRepairJson.codex_login?.status !== 'synced' || !codexLbLegacyAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyAuth.includes('sk-test')) throw new Error('selftest: codex-lb legacy login repair');
|
|
1265
|
+
const codexLbLoginCallsBeforePostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1126
1266
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
1127
1267
|
const codexLbPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1128
1268
|
cwd: tmp,
|
|
1129
|
-
env:
|
|
1130
|
-
...codexLbEnvForSelftest,
|
|
1131
|
-
SKS_POSTINSTALL_NO_BOOTSTRAP: '1',
|
|
1132
|
-
SKS_SKIP_POSTINSTALL_SHIM: '1',
|
|
1133
|
-
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1134
|
-
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1135
|
-
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1136
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1137
|
-
},
|
|
1269
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1138
1270
|
timeoutMs: 15000,
|
|
1139
1271
|
maxOutputBytes: 128 * 1024
|
|
1140
1272
|
});
|
|
1141
1273
|
if (codexLbPostinstall.code !== 0) throw new Error(`selftest: codex-lb postinstall auth preservation exited ${codexLbPostinstall.code}: ${codexLbPostinstall.stderr}`);
|
|
1142
1274
|
const codexLbPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1143
|
-
const codexLbLoginCallsAfterPostinstall =
|
|
1144
|
-
if (!String(codexLbPostinstall.stdout || '').includes('codex-lb auth: preserved') || !codexLbPostinstallAuth.includes('"auth_mode":"
|
|
1145
|
-
const postinstallEnvKeys = ['HOME', 'PATH', 'INIT_CWD', 'SKS_GLOBAL_ROOT', 'SKS_POSTINSTALL_BOOTSTRAP', 'SKS_POSTINSTALL_NO_BOOTSTRAP', 'SKS_SKIP_POSTINSTALL_SHIM', 'SKS_SKIP_POSTINSTALL_CONTEXT7', 'SKS_SKIP_POSTINSTALL_GETDESIGN', 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS', 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH'];
|
|
1275
|
+
const codexLbLoginCallsAfterPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1276
|
+
if (!String(codexLbPostinstall.stdout || '').includes('codex-lb auth: preserved') || !codexLbPostinstallAuth.includes('"auth_mode":"browser"') || codexLbPostinstallAuth.includes('sk-test') || codexLbLoginCallsAfterPostinstall !== codexLbLoginCallsBeforePostinstall) throw new Error('selftest: postinstall auth');
|
|
1277
|
+
const postinstallEnvKeys = ['HOME', 'PATH', 'INIT_CWD', 'SKS_GLOBAL_ROOT', 'SKS_POSTINSTALL_BOOTSTRAP', 'SKS_POSTINSTALL_NO_BOOTSTRAP', 'SKS_SKIP_POSTINSTALL_SHIM', 'SKS_SKIP_POSTINSTALL_CONTEXT7', 'SKS_SKIP_POSTINSTALL_GETDESIGN', 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS', 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH', 'SKS_SKIP_CODEX_LB_LAUNCH_ENV', 'SKS_CODEX_LB_SYNC_CODEX_LOGIN'];
|
|
1146
1278
|
const postinstallEnvBefore = Object.fromEntries(postinstallEnvKeys.map((key) => [key, process.env[key]]));
|
|
1147
|
-
const codexLbLoginCallsBeforeBootstrap =
|
|
1279
|
+
const codexLbLoginCallsBeforeBootstrap = await codexLbLoginCallCount(codexLbHome);
|
|
1148
1280
|
try {
|
|
1149
1281
|
for (const key of postinstallEnvKeys) delete process.env[key];
|
|
1150
1282
|
Object.assign(process.env, {
|
|
@@ -1157,7 +1289,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
1157
1289
|
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1158
1290
|
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1159
1291
|
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1160
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1292
|
+
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1293
|
+
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1'
|
|
1161
1294
|
});
|
|
1162
1295
|
await postinstall({
|
|
1163
1296
|
bootstrap: async () => {
|
|
@@ -1173,8 +1306,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
1173
1306
|
}
|
|
1174
1307
|
const codexLbPostBootstrapAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1175
1308
|
const codexLbPostBootstrapConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1176
|
-
const codexLbLoginCallsAfterBootstrap =
|
|
1177
|
-
if (!codexLbPostBootstrapAuth.includes('"auth_mode":"
|
|
1309
|
+
const codexLbLoginCallsAfterBootstrap = await codexLbLoginCallCount(codexLbHome);
|
|
1310
|
+
if (!codexLbPostBootstrapAuth.includes('"auth_mode":"browser"') || codexLbPostBootstrapAuth.includes('sk-test') || codexLbLoginCallsAfterBootstrap !== codexLbLoginCallsBeforeBootstrap) throw new Error('selftest: postinstall drift auth');
|
|
1178
1311
|
if (hasTopLevelCodexLbSelected(codexLbPostBootstrapConfig) || !codexLbPostBootstrapConfig.includes('[model_providers.codex-lb]') || !codexLbPostBootstrapConfig.includes('https://lb.example.test/backend-api/codex') || codexLbPostBootstrapConfig.includes('sk-test')) throw new Error('selftest: postinstall drift config');
|
|
1179
1312
|
const doctorProject = tmpdir();
|
|
1180
1313
|
await ensureDir(path.join(doctorProject, '.git'));
|
|
@@ -1192,7 +1325,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1192
1325
|
const codexLbDoctorJson = JSON.parse(codexLbDoctorRepair.stdout);
|
|
1193
1326
|
const codexLbDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1194
1327
|
const codexLbDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1195
|
-
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"
|
|
1328
|
+
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"browser"') || codexLbDoctorAuth.includes('sk-test') || hasTopLevelCodexLbSelected(codexLbDoctorConfig) || !codexLbDoctorConfig.includes('https://lb.example.test/backend-api/codex') || !hasCodexUnstableFeatureWarningSuppression(codexLbDoctorConfig)) throw new Error('selftest: doctor codex-lb');
|
|
1196
1329
|
const codexLbContext7Bin = path.join(tmp, 'codex-lb-context7-bin');
|
|
1197
1330
|
await ensureDir(codexLbContext7Bin);
|
|
1198
1331
|
await writeTextAtomic(path.join(codexLbContext7Bin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 99.0.0"; exit 0; fi\nif [ "$CODEX_LB_API_KEY" ]; then echo "context7 leaked CODEX_LB_API_KEY" >&2; exit 77; fi\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then echo ""; exit 0; fi\nif [ "$1" = "mcp" ] && [ "$2" = "add" ]; then echo "context7 added"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
@@ -1214,23 +1347,82 @@ export async function selftestCodexLb(tmp) {
|
|
|
1214
1347
|
});
|
|
1215
1348
|
if (codexLbContext7Postinstall.code !== 0 || String(`${codexLbContext7Postinstall.stdout}\n${codexLbContext7Postinstall.stderr}`).includes('leaked CODEX_LB_API_KEY')) throw new Error('selftest: Context7 key leak');
|
|
1216
1349
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_API_KEY='unterminated\n");
|
|
1217
|
-
const codexLbLoginCallsBeforeMalformed =
|
|
1350
|
+
const codexLbLoginCallsBeforeMalformed = await codexLbLoginCallCount(codexLbHome);
|
|
1218
1351
|
const codexLbMalformedPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1219
1352
|
cwd: tmp,
|
|
1220
|
-
env:
|
|
1221
|
-
...codexLbEnvForSelftest,
|
|
1222
|
-
SKS_POSTINSTALL_NO_BOOTSTRAP: '1',
|
|
1223
|
-
SKS_SKIP_POSTINSTALL_SHIM: '1',
|
|
1224
|
-
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1225
|
-
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1226
|
-
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1227
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1228
|
-
},
|
|
1353
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1229
1354
|
timeoutMs: 15000,
|
|
1230
1355
|
maxOutputBytes: 128 * 1024
|
|
1231
1356
|
});
|
|
1232
|
-
const codexLbLoginCallsAfterMalformed =
|
|
1357
|
+
const codexLbLoginCallsAfterMalformed = await codexLbLoginCallCount(codexLbHome);
|
|
1233
1358
|
if (codexLbMalformedPostinstall.code !== 0 || !String(codexLbMalformedPostinstall.stdout || '').includes('codex-lb auth: stored key missing') || codexLbLoginCallsAfterMalformed !== codexLbLoginCallsBeforeMalformed) throw new Error('selftest: bad codex-lb env');
|
|
1359
|
+
await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
|
|
1360
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), '[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
|
|
1361
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy"}\n');
|
|
1362
|
+
const codexLbLoginCallsBeforeLegacyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1363
|
+
const codexLbLegacyPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1364
|
+
cwd: tmp,
|
|
1365
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1366
|
+
timeoutMs: 15000,
|
|
1367
|
+
maxOutputBytes: 128 * 1024
|
|
1368
|
+
});
|
|
1369
|
+
if (codexLbLegacyPostinstall.code !== 0) throw new Error(`selftest: legacy codex-lb postinstall restore exited ${codexLbLegacyPostinstall.code}: ${codexLbLegacyPostinstall.stderr}`);
|
|
1370
|
+
const codexLbLegacyPostinstallEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1371
|
+
const codexLbLegacyPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1372
|
+
const codexLbLoginCallsAfterLegacyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1373
|
+
if (!String(codexLbLegacyPostinstall.stdout || '').includes('codex-lb auth: restored from existing Codex login cache') || !codexLbLegacyPostinstallEnv.includes("CODEX_LB_API_KEY='sk-legacy'") || !codexLbLegacyPostinstallEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbLegacyPostinstallAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyPostinstallAuth.includes('sk-legacy') || codexLbLoginCallsAfterLegacyPostinstall !== codexLbLoginCallsBeforeLegacyPostinstall) throw new Error('selftest: legacy codex-lb postinstall restore');
|
|
1374
|
+
await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
|
|
1375
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
|
|
1376
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy-doctor"}\n');
|
|
1377
|
+
const codexLbLegacyDoctorProject = tmpdir();
|
|
1378
|
+
await ensureDir(path.join(codexLbLegacyDoctorProject, '.git'));
|
|
1379
|
+
await writeTextAtomic(path.join(codexLbLegacyDoctorProject, 'package.json'), '{"name":"codex-lb-legacy-doctor-project","version":"0.0.0"}\n');
|
|
1380
|
+
const codexLbLegacyDoctorRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--json'], {
|
|
1381
|
+
cwd: codexLbLegacyDoctorProject,
|
|
1382
|
+
env: { ...codexLbEnvForSelftest, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-legacy-doctor-global'), SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1' },
|
|
1383
|
+
timeoutMs: 30000,
|
|
1384
|
+
maxOutputBytes: 256 * 1024
|
|
1385
|
+
});
|
|
1386
|
+
if (codexLbLegacyDoctorRepair.code !== 0) throw new Error(`selftest: legacy doctor --fix codex-lb restore exited ${codexLbLegacyDoctorRepair.code}: ${codexLbLegacyDoctorRepair.stderr}`);
|
|
1387
|
+
const codexLbLegacyDoctorJson = JSON.parse(codexLbLegacyDoctorRepair.stdout);
|
|
1388
|
+
const codexLbLegacyDoctorEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1389
|
+
const codexLbLegacyDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1390
|
+
const codexLbLegacyDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1391
|
+
if (!codexLbLegacyDoctorJson.repair?.codex_lb?.ok || !codexLbLegacyDoctorJson.repair.codex_lb.legacy_auth_migrated || !codexLbLegacyDoctorEnv.includes("CODEX_LB_API_KEY='sk-legacy-doctor'") || !codexLbLegacyDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyDoctorAuth.includes('sk-legacy-doctor') || hasTopLevelCodexLbSelected(codexLbLegacyDoctorConfig) || !codexLbLegacyDoctorConfig.includes('env_key = "CODEX_LB_API_KEY"')) throw new Error('selftest: legacy doctor codex-lb restore');
|
|
1392
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\n");
|
|
1393
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nservice_tier = "fast"\n');
|
|
1394
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-env-only"}\n');
|
|
1395
|
+
const codexLbLoginCallsBeforeEnvOnlyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1396
|
+
const codexLbEnvOnlyPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1397
|
+
cwd: tmp,
|
|
1398
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1399
|
+
timeoutMs: 15000,
|
|
1400
|
+
maxOutputBytes: 128 * 1024
|
|
1401
|
+
});
|
|
1402
|
+
if (codexLbEnvOnlyPostinstall.code !== 0) throw new Error(`selftest: env-only codex-lb postinstall restore exited ${codexLbEnvOnlyPostinstall.code}: ${codexLbEnvOnlyPostinstall.stderr}`);
|
|
1403
|
+
const codexLbEnvOnlyPostinstallEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1404
|
+
const codexLbEnvOnlyPostinstallConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1405
|
+
const codexLbEnvOnlyPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1406
|
+
const codexLbLoginCallsAfterEnvOnlyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1407
|
+
if (!String(codexLbEnvOnlyPostinstall.stdout || '').includes('codex-lb auth: restored from existing Codex login cache') || !codexLbEnvOnlyPostinstallEnv.includes("CODEX_LB_API_KEY='sk-env-only'") || !codexLbEnvOnlyPostinstallConfig.includes('env_key = "CODEX_LB_API_KEY"') || !codexLbEnvOnlyPostinstallAuth.includes('sk-env-only') || codexLbLoginCallsAfterEnvOnlyPostinstall !== codexLbLoginCallsBeforeEnvOnlyPostinstall) throw new Error('selftest: env-only codex-lb postinstall restore');
|
|
1408
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\n");
|
|
1409
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nservice_tier = "fast"\n');
|
|
1410
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-env-only-doctor"}\n');
|
|
1411
|
+
const codexLbEnvOnlyDoctorProject = tmpdir();
|
|
1412
|
+
await ensureDir(path.join(codexLbEnvOnlyDoctorProject, '.git'));
|
|
1413
|
+
await writeTextAtomic(path.join(codexLbEnvOnlyDoctorProject, 'package.json'), '{"name":"codex-lb-env-only-doctor-project","version":"0.0.0"}\n');
|
|
1414
|
+
const codexLbEnvOnlyDoctorRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--json'], {
|
|
1415
|
+
cwd: codexLbEnvOnlyDoctorProject,
|
|
1416
|
+
env: { ...codexLbEnvForSelftest, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-env-only-doctor-global'), SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1' },
|
|
1417
|
+
timeoutMs: 30000,
|
|
1418
|
+
maxOutputBytes: 256 * 1024
|
|
1419
|
+
});
|
|
1420
|
+
if (codexLbEnvOnlyDoctorRepair.code !== 0) throw new Error(`selftest: env-only doctor --fix codex-lb restore exited ${codexLbEnvOnlyDoctorRepair.code}: ${codexLbEnvOnlyDoctorRepair.stderr}`);
|
|
1421
|
+
const codexLbEnvOnlyDoctorJson = JSON.parse(codexLbEnvOnlyDoctorRepair.stdout);
|
|
1422
|
+
const codexLbEnvOnlyDoctorEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1423
|
+
const codexLbEnvOnlyDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1424
|
+
const codexLbEnvOnlyDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1425
|
+
if (!codexLbEnvOnlyDoctorJson.repair?.codex_lb?.ok || !codexLbEnvOnlyDoctorJson.repair.codex_lb.legacy_auth_migrated || !codexLbEnvOnlyDoctorEnv.includes("CODEX_LB_API_KEY='sk-env-only-doctor'") || !codexLbEnvOnlyDoctorConfig.includes('env_key = "CODEX_LB_API_KEY"') || !codexLbEnvOnlyDoctorAuth.includes('sk-env-only-doctor')) throw new Error('selftest: env-only doctor codex-lb restore');
|
|
1234
1426
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_API_KEY='sk-test'\n");
|
|
1235
1427
|
const codexLbMissingCli = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1236
1428
|
cwd: tmp,
|
|
@@ -1243,12 +1435,13 @@ export async function selftestCodexLb(tmp) {
|
|
|
1243
1435
|
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1244
1436
|
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1245
1437
|
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1246
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1438
|
+
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1439
|
+
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1'
|
|
1247
1440
|
},
|
|
1248
1441
|
timeoutMs: 15000,
|
|
1249
1442
|
maxOutputBytes: 128 * 1024
|
|
1250
1443
|
});
|
|
1251
|
-
if (codexLbMissingCli.code !== 0 || !String(codexLbMissingCli.stdout || '').includes('codex-lb auth:
|
|
1444
|
+
if (codexLbMissingCli.code !== 0 || !String(codexLbMissingCli.stdout || '').includes('codex-lb auth: preserved') || String(codexLbMissingCli.stdout || '').includes('codex_missing')) throw new Error('selftest: codex-lb provider auth should not require Codex CLI login');
|
|
1252
1445
|
const codexLbNotConfiguredHome = path.join(tmp, 'codex-lb-not-configured-home');
|
|
1253
1446
|
const codexLbNotConfigured = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1254
1447
|
cwd: tmp,
|
|
@@ -1268,7 +1461,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1268
1461
|
});
|
|
1269
1462
|
if (codexLbNotConfigured.code !== 0 || String(codexLbNotConfigured.stdout || '').includes('codex-lb auth:')) throw new Error('selftest: postinstall should stay quiet when codex-lb is not configured');
|
|
1270
1463
|
const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
1271
|
-
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest: codex-lb status did not advertise repair command');
|
|
1464
|
+
if (!String(codexLbStatusText.stdout || '').includes('Repair provider auth: sks codex-lb repair')) throw new Error('selftest: codex-lb status did not advertise repair command');
|
|
1272
1465
|
const nonInteractiveLaunchChainCalls = [];
|
|
1273
1466
|
const nonInteractiveLaunch = await maybePromptCodexLbSetupForLaunch([], {
|
|
1274
1467
|
home: codexLbHome,
|
package/src/cli/main.mjs
CHANGED
|
@@ -1149,7 +1149,7 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1149
1149
|
console.log(`Env file: ${status.env_file ? status.env_path : 'missing'}`);
|
|
1150
1150
|
if (status.base_url) console.log(`Base URL: ${status.base_url}`);
|
|
1151
1151
|
if (!status.ok) console.log('\nRun: sks codex-lb setup --host <domain> --api-key <key>');
|
|
1152
|
-
else console.log('\nRepair auth: sks codex-lb repair');
|
|
1152
|
+
else console.log('\nRepair provider auth: sks codex-lb repair');
|
|
1153
1153
|
return;
|
|
1154
1154
|
}
|
|
1155
1155
|
if (sub === 'health' || sub === 'verify-chain' || sub === 'chain') {
|
|
@@ -1176,7 +1176,7 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1176
1176
|
process.exitCode = 1;
|
|
1177
1177
|
return;
|
|
1178
1178
|
}
|
|
1179
|
-
console.log('codex-lb auth repaired for Codex CLI.');
|
|
1179
|
+
console.log('codex-lb provider auth repaired for Codex CLI/App environment.');
|
|
1180
1180
|
console.log(`Config: ${result.config_path}`);
|
|
1181
1181
|
console.log(`Key env: ${result.env_path}`);
|
|
1182
1182
|
return;
|
|
@@ -1193,7 +1193,7 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1193
1193
|
const result = await configureCodexLb({ host, apiKey });
|
|
1194
1194
|
if (json) return console.log(JSON.stringify(result, null, 2));
|
|
1195
1195
|
if (!result.ok) {
|
|
1196
|
-
console.error(`codex-lb setup failed: ${result.status}`);
|
|
1196
|
+
console.error(`codex-lb setup failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
|
|
1197
1197
|
process.exitCode = 1;
|
|
1198
1198
|
return;
|
|
1199
1199
|
}
|
|
@@ -1779,7 +1779,7 @@ async function doctor(args) {
|
|
|
1779
1779
|
const cleanup = removed.length ? ` removed stale generated skill shadow(s): ${removed.join(', ')}` : '';
|
|
1780
1780
|
console.log(`Global $ repair: ${globalSkillsRepair.status} ${globalSkillsRepair.root || ''}${cleanup}`.trimEnd());
|
|
1781
1781
|
}
|
|
1782
|
-
if (codexLbRepair?.ok) console.log(`codex-lb repair: ${codexLbRepair.config_repaired ? 'config+auth resynced' : 'auth resynced'} from stored env`);
|
|
1782
|
+
if (codexLbRepair?.ok) console.log(`codex-lb repair: ${codexLbRepair.config_repaired ? 'config+provider auth resynced' : 'provider auth resynced'} from stored env`);
|
|
1783
1783
|
else if (codexLbRepair && codexLbRepair.status !== 'missing_env_key') console.log(`codex-lb repair: skipped (${codexLbRepair.status})`);
|
|
1784
1784
|
if (flag(args, '--fix') && result.harness_conflicts.hard_block) console.log('Repair: skipped because another Codex harness needs human-approved removal first');
|
|
1785
1785
|
console.log(`Rust acc.: ${rust.available ? rust.version : 'optional-missing'}`);
|
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.8.
|
|
8
|
+
export const PACKAGE_VERSION = '0.8.5';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
package/src/core/routes.mjs
CHANGED
|
@@ -523,8 +523,8 @@ export const COMMAND_CATALOG = [
|
|
|
523
523
|
{ name: 'root', usage: 'sks root [--json]', description: 'Show whether SKS is using a project root or the per-user global SKS runtime root.' },
|
|
524
524
|
{ name: 'deps', usage: 'sks deps check|install [tmux|codex|context7|all] [--yes]', description: 'Check or guided-install Node/npm PATH, Codex CLI/App, Context7, Browser tooling, Computer Use, tmux, and Homebrew on macOS.' },
|
|
525
525
|
{ name: 'codex-app', usage: 'sks codex-app [check|open|remote-control]', description: 'Check Codex App install and first-party MCP/plugin readiness, then show app setup files, examples, and Codex CLI 0.130.0+ remote-control availability.' },
|
|
526
|
-
{ name: 'codex-lb', usage: 'sks codex-lb status|health|repair|setup --host <domain> --api-key <key>', description: 'Configure, health-check, or repair codex-lb
|
|
527
|
-
{ name: 'auth', usage: 'sks auth status|health|repair|setup --host <domain> --api-key <key>', description: 'Shortcut for codex-lb auth status, health, repair, and setup commands.' },
|
|
526
|
+
{ name: 'codex-lb', usage: 'sks codex-lb status|health|repair|setup --host <domain> --api-key <key>', description: 'Configure, health-check, or repair codex-lb provider auth by writing ~/.codex/config.toml, restoring CODEX_LB_API_KEY env auth from stored or legacy login-cache state, and preserving the shared Codex login cache unless explicitly requested.' },
|
|
527
|
+
{ name: 'auth', usage: 'sks auth status|health|repair|setup --host <domain> --api-key <key>', description: 'Shortcut for codex-lb provider auth status, health, repair, and setup commands.' },
|
|
528
528
|
{ name: 'openclaw', usage: 'sks openclaw install|path|print [--dir path] [--force] [--json]', description: 'Generate an OpenClaw skill package so OpenClaw agents can discover and use local SKS workflows.' },
|
|
529
529
|
{ name: 'tmux', usage: 'sks | sks tmux open|check|status [--workspace name]', description: 'Open the default SKS tmux runtime with bare sks, or use tmux subcommands for explicit launch/check/status.' },
|
|
530
530
|
{ name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot tmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
|