sneakoscope 0.7.67 → 0.7.68
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 +19 -135
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +77 -35
- package/src/cli/main.mjs +699 -662
- package/src/cli/maintenance-commands.mjs +67 -3
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +18 -5
- package/src/core/init.mjs +30 -5
- package/src/core/pipeline.mjs +1 -1
- package/src/core/research.mjs +400 -11
- package/src/core/routes.mjs +3 -3
package/README.md
CHANGED
|
@@ -38,25 +38,7 @@ sks selftest --mock
|
|
|
38
38
|
|
|
39
39
|
## What Sneakoscope Adds
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
| --- | --- |
|
|
43
|
-
| CLI runtime | Bare `sks` opens/reuses the default tmux Codex CLI workspace. `sks tmux open` handles explicit session flags, and `sks --mad` launches a single-pane MAD session. |
|
|
44
|
-
| Codex App commands | Installs generated skills for `$Team`, `$DFix`, `$QA-LOOP`, `$PPT`, `$UX-Review`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes. |
|
|
45
|
-
| OpenClaw agents | Generates an OpenClaw skill package so agents can attach `sneakoscope-codex` and discover SKS commands from the repo root. |
|
|
46
|
-
| Pipeline plans | Writes `pipeline-plan.json` so runtime lanes, stages, verification, and no-fallback invariants are visible with `sks pipeline plan`. |
|
|
47
|
-
| Team orchestration | Runs substantial work through ambiguity handling, scouts, TriWiki, debate, runtime tasks, implementation, review, cleanup, reflection, and Honest Mode; narrow work should use Proof Field evidence. |
|
|
48
|
-
| Skill dreaming | Records cheap generated-skill usage counters in JSON and only periodically scans `.agents/skills` for keep, merge, prune, and improvement candidates. Reports are recommendation-only and never delete skills automatically. |
|
|
49
|
-
| From-Chat-IMG | Turns chat screenshots plus original attachments into source-bound work orders, then requires scoped QA evidence before completion. |
|
|
50
|
-
| QA loop | Dogfoods UI/API behavior with safety gates, Codex Computer Use-only UI evidence, safe fixes, and rechecks. |
|
|
51
|
-
| PPT pipeline | Uses `$PPT` for restrained HTML/PDF decks with sealed context, design SSOT, export QA, editable source HTML, and `$imagegen` assets when required. |
|
|
52
|
-
| Image UX Review | Uses `$Image-UX-Review` / `$UX-Review` for UI/UX audits that require generated annotated review images before issue extraction. |
|
|
53
|
-
| Computer Use fast lane | Uses `$Computer-Use` / `$CU` for fast UI/browser/visual work, then refreshes/validates TriWiki and runs Honest Mode. |
|
|
54
|
-
| Goal | Bridges Codex native `/goal` create, pause, resume, and clear controls while implementation continues through the selected SKS route. |
|
|
55
|
-
| TriWiki voxels | Maintains `.sneakoscope/wiki/context-pack.json` as the context SSOT with coordinate anchors, voxel metadata, attention hints, and mistake recall. |
|
|
56
|
-
| Context7 | Requires current docs for external packages, APIs, MCPs, SDKs, and framework/runtime behavior when correctness depends on current guidance. |
|
|
57
|
-
| Design SSOT | Treats `design.md` as the only design decision source of truth; getdesign and curated DESIGN.md examples are inputs, not parallel authorities. |
|
|
58
|
-
| DB safety | Treats SQL, migrations, Supabase, RLS, and destructive operations as high risk. |
|
|
59
|
-
| Release hygiene | Checks versioning, changelog, size, syntax. |
|
|
41
|
+
`sks` adds a tmux Codex CLI runtime, Codex App `$` commands, Team/QA/PPT/Research/DB/GX/Wiki routes, OpenClaw skill generation, Context7-gated current docs, TriWiki context packs, DB safety, design SSOT policy, skill dreaming, release checks, and Honest Mode.
|
|
60
42
|
|
|
61
43
|
## Requirements
|
|
62
44
|
|
|
@@ -172,9 +154,9 @@ sks codex-lb repair
|
|
|
172
154
|
sks
|
|
173
155
|
```
|
|
174
156
|
|
|
175
|
-
Bare `sks` can also prompt for codex-lb auth
|
|
157
|
+
Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, syncs `codex login --with-api-key`, and loads it in tmux.
|
|
176
158
|
|
|
177
|
-
If Codex CLI auth drifts after
|
|
159
|
+
If Codex CLI 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>`.
|
|
178
160
|
|
|
179
161
|
### MAD tmux Launch
|
|
180
162
|
|
|
@@ -210,6 +192,8 @@ sks qa-loop prepare "http://localhost:3000"
|
|
|
210
192
|
sks qa-loop run latest --max-cycles 2
|
|
211
193
|
sks goal create "persist this migration workflow"
|
|
212
194
|
sks research prepare "evaluate this approach"
|
|
195
|
+
sks research run latest --max-cycles 3
|
|
196
|
+
sks research status latest
|
|
213
197
|
sks db scan --json
|
|
214
198
|
sks wiki refresh
|
|
215
199
|
sks wiki sweep latest --json
|
|
@@ -227,6 +211,8 @@ sks skill-dream run --json
|
|
|
227
211
|
sks code-structure scan --json
|
|
228
212
|
```
|
|
229
213
|
|
|
214
|
+
`sks research` prepares a genius-lens scout council, requires every scout to run at `xhigh`, records one literal `Eureka!` idea per scout, runs an evidence-bound debate, maximizes available web/source retrieval before synthesis, and requires `source-ledger.json`, `scout-ledger.json`, `debate-ledger.json`, `novelty-ledger.json`, `falsification-ledger.json`, and `research-gate.json` so research runs stay source-backed, adversarially checked, and falsifiable. `research status` reports source entries, counterevidence, xhigh scout count, Eureka moments, debate exchanges, scout findings, and falsification cases alongside the gate.
|
|
215
|
+
|
|
230
216
|
`sks pipeline plan` shows the active route lane, kept/skipped stages, verification commands, and no-unrequested-fallback invariant. `sks proof-field scan` is the lightweight rubric for small changes; risky or broad signals return to the full Team/Honest path.
|
|
231
217
|
|
|
232
218
|
### Ambiguity Questions
|
|
@@ -273,6 +259,7 @@ $DFix change this label and spacing only
|
|
|
273
259
|
$QA-LOOP dogfood localhost:3000 and fix safe issues
|
|
274
260
|
$PPT create an investor deck as HTML/PDF
|
|
275
261
|
$Goal persist this migration workflow with native /goal continuation
|
|
262
|
+
$Research investigate this mechanism with source-backed scout lenses
|
|
276
263
|
$Wiki refresh and validate the context pack
|
|
277
264
|
$DB inspect this migration for destructive risk
|
|
278
265
|
```
|
|
@@ -302,41 +289,7 @@ sks openclaw install
|
|
|
302
289
|
sks openclaw path
|
|
303
290
|
```
|
|
304
291
|
|
|
305
|
-
By default this writes
|
|
306
|
-
|
|
307
|
-
```text
|
|
308
|
-
~/.openclaw/skills/sneakoscope-codex/
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
The generated skill contains `manifest.yaml`, `SKILL.md`, a skill README, and `openclaw-agent-config.example.yaml`. If you use a custom OpenClaw home, set `OPENCLAW_HOME` or pass `--dir`:
|
|
312
|
-
|
|
313
|
-
```sh
|
|
314
|
-
OPENCLAW_HOME=/opt/openclaw sks openclaw install
|
|
315
|
-
sks openclaw install --dir /opt/openclaw/skills/sneakoscope-codex
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
Attach the skill to an OpenClaw agent with the built-in `shell` tool enabled:
|
|
319
|
-
|
|
320
|
-
```yaml
|
|
321
|
-
agents:
|
|
322
|
-
coding-agent:
|
|
323
|
-
tools:
|
|
324
|
-
- shell
|
|
325
|
-
env:
|
|
326
|
-
SKS_OPENCLAW: "1"
|
|
327
|
-
skills:
|
|
328
|
-
- sneakoscope-codex
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
`SKS_OPENCLAW=1` tells SKS that commands are running from OpenClaw. In that mode, SKS auto-approves update/install prompts such as the Codex CLI update check before tmux launch, instead of waiting for a human `Y/n` response.
|
|
332
|
-
|
|
333
|
-
Then prompt the OpenClaw agent from the target repo root:
|
|
334
|
-
|
|
335
|
-
```text
|
|
336
|
-
Run sks root, inspect AGENTS.md, then use the SKS Team route to implement this fix and verify it.
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
Useful commands for OpenClaw agents:
|
|
292
|
+
By default this writes `~/.openclaw/skills/sneakoscope-codex/` with `manifest.yaml`, `SKILL.md`, a README, and `openclaw-agent-config.example.yaml`. Set `OPENCLAW_HOME` or pass `--dir` for a custom location. Attach the skill with the built-in `shell` tool enabled and set `SKS_OPENCLAW=1` so SKS can auto-approve update/install prompts that would otherwise wait for `Y/n`.
|
|
340
293
|
|
|
341
294
|
```sh
|
|
342
295
|
SKS_OPENCLAW=1 sks root
|
|
@@ -346,114 +299,43 @@ SKS_OPENCLAW=1 sks deps check
|
|
|
346
299
|
SKS_OPENCLAW=1 sks proof-field scan --intent "small CLI change" --changed src/cli/main.mjs
|
|
347
300
|
```
|
|
348
301
|
|
|
349
|
-
If OpenClaw runs
|
|
302
|
+
If OpenClaw runs in a sandbox, grant shell execution only for trusted workspaces. Database, migration, and destructive work still follows SKS safety routes.
|
|
350
303
|
|
|
351
304
|
## Prompt `$` Commands
|
|
352
305
|
|
|
353
306
|
Use these inside Codex App or another agent prompt. They are prompt commands, not terminal commands.
|
|
354
307
|
|
|
355
|
-
|
|
356
|
-
| --- | --- |
|
|
357
|
-
| `$Team` | You want implementation, code changes, or substantial repo work. |
|
|
358
|
-
| `$From-Chat-IMG` | You have a chat screenshot plus original attachments and want each visible request mapped to work. |
|
|
359
|
-
| `$DFix` | You need Direct Fix work: tiny copy/config/docs/labels/spacing/translation/simple mechanical edits, with broad implementation still routed to Team and UI design specifics handled by the relevant UI/design route rules. |
|
|
360
|
-
| `$Answer` | You want an answer only and no implementation should start. |
|
|
361
|
-
| `$SKS` | You need setup, status, usage, or workflow help. |
|
|
362
|
-
| `$QA-LOOP` | You want UI/API dogfooding, safe fixes, and rechecks. |
|
|
363
|
-
| `$PPT` | You want a restrained HTML/PDF presentation with sealed delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/solution/aha mappings. |
|
|
364
|
-
| `$Computer-Use` / `$CU` | You want the fastest Codex Computer Use lane for UI/browser/visual inspection or small safe fixes. |
|
|
365
|
-
| `$Goal` | You want a fast SKS bridge overlay for Codex native persisted `/goal` continuation. |
|
|
366
|
-
| `$Research` | You need frontier-style research with hypotheses and falsification. |
|
|
367
|
-
| `$AutoResearch` | You want iterative improve/test/keep-or-discard optimization. |
|
|
368
|
-
| `$DB` | You need database, Supabase, migration, SQL, or MCP safety checks. |
|
|
369
|
-
| `$MAD-SKS` | You explicitly authorize scoped Supabase MCP DB cleanup/write permissions for the active invocation only, while keeping catastrophic wipe safeguards. |
|
|
370
|
-
| `$GX` | You need deterministic visual context cartridges. |
|
|
371
|
-
| `$Wiki` | You want TriWiki refresh, pack, prune, validate, or maintenance. |
|
|
372
|
-
| `$Help` | You want installed command and workflow explanation. |
|
|
308
|
+
Common prompts: `$Team`, `$From-Chat-IMG`, `$DFix`, `$Answer`, `$SKS`, `$QA-LOOP`, `$PPT`, `$Computer-Use`/`$CU`, `$Goal`, `$Research`, `$AutoResearch`, `$DB`, `$MAD-SKS`, `$GX`, `$Wiki`, and `$Help`.
|
|
373
309
|
|
|
374
310
|
## Common Workflows
|
|
375
311
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
1. Install SKS.
|
|
312
|
+
First install:
|
|
379
313
|
|
|
380
314
|
```sh
|
|
381
315
|
npm i -g sneakoscope
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
2. Bootstrap and check dependencies.
|
|
385
|
-
|
|
386
|
-
```sh
|
|
387
316
|
sks bootstrap
|
|
388
317
|
sks deps check
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
On macOS, missing tmux installs and Homebrew-managed tmux upgrades ask `Y/n` before running `brew install tmux` or `brew upgrade tmux`. If PATH resolves an npm-managed `tmux`, SKS prompts for `npm i -g tmux@latest` instead of using Homebrew. Unknown non-Homebrew `tmux` paths are reported as conflicts so the user can remove, upgrade with the owning package manager, or reorder PATH first.
|
|
392
|
-
|
|
393
|
-
3. Confirm Codex App command surfaces.
|
|
394
|
-
|
|
395
|
-
```sh
|
|
396
318
|
sks codex-app check
|
|
397
|
-
sks dollar-commands
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
4. Optional codex-lb key setup for CLI `sks` runs.
|
|
401
|
-
|
|
402
|
-
```sh
|
|
403
|
-
sks codex-lb setup --host <domain> --api-key <key>
|
|
404
|
-
sks codex-lb repair
|
|
405
|
-
sks
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
5. Run a local smoke test.
|
|
409
|
-
|
|
410
|
-
```sh
|
|
411
319
|
sks selftest --mock
|
|
412
320
|
```
|
|
413
321
|
|
|
414
|
-
|
|
322
|
+
Start a CLI workspace:
|
|
415
323
|
|
|
416
324
|
```sh
|
|
417
325
|
sks tmux check
|
|
418
326
|
sks
|
|
327
|
+
# or: sks --mad
|
|
419
328
|
```
|
|
420
329
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
For the high-reasoning full-access profile:
|
|
424
|
-
|
|
425
|
-
```sh
|
|
426
|
-
sks --mad
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### Use Codex App `$Team`
|
|
430
|
-
|
|
431
|
-
```text
|
|
432
|
-
$Team implement the requested change, update docs if needed, and verify with the relevant tests
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
Team mode records a mission under `.sneakoscope/missions/`, keeps a live transcript, uses TriWiki context, and finishes with evidence and Honest Mode.
|
|
436
|
-
Every new Team mission now also writes `work-order-ledger.json`, `effort-decision.json`, and `team-dashboard-state.json`. Run `sks validate-artifacts latest` to check the schema gates before treating mission artifacts as completion evidence.
|
|
437
|
-
|
|
438
|
-
### Dogfood A UI Or API
|
|
439
|
-
|
|
440
|
-
```sh
|
|
441
|
-
sks qa-loop prepare "http://localhost:3000"
|
|
442
|
-
sks qa-loop run latest --max-cycles 2
|
|
443
|
-
sks qa-loop status latest
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
Use `$QA-LOOP` in Codex App when UI-level E2E needs verification. UI verification must use Codex Computer Use evidence only; Chrome MCP, Browser Use, Playwright, Selenium, Puppeteer, and other browser automation do not satisfy UI-level E2E verification.
|
|
330
|
+
Use Codex App routes with `$Team`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$Wiki`, and `$DB`. Team missions write artifacts under `.sneakoscope/missions/`; validate them with `sks validate-artifacts latest`.
|
|
447
331
|
|
|
448
|
-
|
|
332
|
+
Refresh context before risky work:
|
|
449
333
|
|
|
450
334
|
```sh
|
|
451
335
|
sks wiki refresh
|
|
452
336
|
sks wiki validate .sneakoscope/wiki/context-pack.json
|
|
453
337
|
```
|
|
454
338
|
|
|
455
|
-
TriWiki is the long-running context source of truth. It keeps compact high-trust recall in `attention.use_first`, source-hydration targets in `attention.hydrate_first`, and binds relevant prior-mistake claims into the current decision contract when they match the prompt.
|
|
456
|
-
|
|
457
339
|
## Safety Model
|
|
458
340
|
|
|
459
341
|
Sneakoscope intentionally treats these as high-risk:
|
|
@@ -527,10 +409,12 @@ npm run changelog:check
|
|
|
527
409
|
npm run packcheck
|
|
528
410
|
npm run selftest
|
|
529
411
|
npm run sizecheck
|
|
412
|
+
npm run registry:check
|
|
530
413
|
npm run release:check
|
|
414
|
+
npm run publish:dry
|
|
531
415
|
```
|
|
532
416
|
|
|
533
|
-
`release:check` runs audit, changelog, syntax, selftest, size, and registry checks.
|
|
417
|
+
`release:check` runs audit, changelog, syntax, selftest, size, and registry checks. `publish:dry` runs that same gate and then performs an npm dry-run publish against the public registry.
|
|
534
418
|
|
|
535
419
|
## License
|
|
536
420
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.68",
|
|
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",
|
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import fsp from 'node:fs/promises';
|
|
4
4
|
import readline from 'node:readline/promises';
|
|
5
5
|
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
-
import { ensureDir, exists, globalSksRoot, packageRoot, readText, runProcess, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
6
|
+
import { ensureDir, exists, globalSksRoot, packageRoot, readText, runProcess, tmpdir, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
7
7
|
import { getCodexInfo } from '../core/codex-adapter.mjs';
|
|
8
8
|
import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.mjs';
|
|
9
9
|
import { initProject, installSkills } from '../core/init.mjs';
|
|
@@ -71,9 +71,10 @@ export async function postinstall({ bootstrap }) {
|
|
|
71
71
|
|
|
72
72
|
async function reportPostinstallCodexLbAuth() {
|
|
73
73
|
const codexLbAuth = await ensureCodexLbAuthDuringInstall();
|
|
74
|
-
if (codexLbAuth.status === 'synced' || codexLbAuth.status === 'present') console.log(`codex-lb auth: preserved from ${codexLbAuth.env_path}.`);
|
|
74
|
+
if (codexLbAuth.status === 'synced' || codexLbAuth.status === 'present' || codexLbAuth.status === 'repaired') console.log(`codex-lb auth: preserved from ${codexLbAuth.env_path}.`);
|
|
75
75
|
else if (codexLbAuth.status === 'skipped') console.log(`codex-lb auth: skipped (${codexLbAuth.reason}).`);
|
|
76
76
|
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.');
|
|
77
|
+
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.');
|
|
77
78
|
else if (codexLbAuth.status && codexLbAuth.status !== 'not_configured') console.log(`codex-lb auth: repair skipped (${codexLbAuth.status}${codexLbAuth.error ? `: ${codexLbAuth.error}` : ''}).`);
|
|
78
79
|
return codexLbAuth;
|
|
79
80
|
}
|
|
@@ -148,11 +149,11 @@ async function capturePostinstallCodexLbConfigSnapshot(home = process.env.HOME |
|
|
|
148
149
|
const configPath = codexLbConfigPath(home);
|
|
149
150
|
const envPath = codexLbEnvPath(home);
|
|
150
151
|
const config = await readText(configPath, '');
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
const envText = await readText(envPath, '');
|
|
153
|
+
if (!parseCodexLbEnvKey(envText)) return null;
|
|
154
|
+
const baseUrl = codexLbProviderBaseUrl(config) || parseCodexLbEnvBaseUrl(envText);
|
|
153
155
|
if (!baseUrl) return null;
|
|
154
|
-
|
|
155
|
-
return { config_path: configPath, env_path: envPath, base_url: baseUrl };
|
|
156
|
+
return { config_path: configPath, env_path: envPath, base_url: normalizeCodexLbBaseUrl(baseUrl) };
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
async function restorePostinstallCodexLbConfigSnapshot(snapshot) {
|
|
@@ -185,7 +186,7 @@ export async function configureCodexLb(opts = {}) {
|
|
|
185
186
|
const current = await readText(configPath, '');
|
|
186
187
|
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, baseUrl));
|
|
187
188
|
await writeTextAtomic(configPath, next);
|
|
188
|
-
await writeTextAtomic(envPath, `export CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
189
|
+
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
189
190
|
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
190
191
|
process.env.CODEX_LB_API_KEY = apiKey;
|
|
191
192
|
const codexLogin = await syncCodexApiKeyLogin(apiKey, { home, force: true });
|
|
@@ -202,15 +203,17 @@ export async function codexLbStatus(opts = {}) {
|
|
|
202
203
|
const envKeyConfigured = Boolean(parseCodexLbEnvKey(envText));
|
|
203
204
|
const providerConfigured = /\[model_providers\.codex-lb\]/.test(config);
|
|
204
205
|
const selected = /model_provider\s*=\s*"codex-lb"/.test(config);
|
|
206
|
+
const baseUrl = codexLbProviderBaseUrl(config) || parseCodexLbEnvBaseUrl(envText) || null;
|
|
205
207
|
return {
|
|
206
|
-
ok: selected && providerConfigured && envKeyConfigured,
|
|
208
|
+
ok: selected && providerConfigured && envKeyConfigured && Boolean(baseUrl),
|
|
207
209
|
config_path: configPath,
|
|
208
210
|
env_path: envPath,
|
|
209
211
|
provider_configured: providerConfigured,
|
|
210
212
|
selected,
|
|
211
213
|
env_file: envExists,
|
|
212
214
|
env_key_configured: envKeyConfigured,
|
|
213
|
-
|
|
215
|
+
env_base_url_configured: Boolean(parseCodexLbEnvBaseUrl(envText)),
|
|
216
|
+
base_url: baseUrl
|
|
214
217
|
};
|
|
215
218
|
}
|
|
216
219
|
|
|
@@ -225,11 +228,20 @@ function codexLbProviderBaseUrl(text = '') {
|
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
export async function repairCodexLbAuth(opts = {}) {
|
|
228
|
-
|
|
231
|
+
let status = await codexLbStatus(opts);
|
|
232
|
+
let configRepaired = false;
|
|
233
|
+
if (!status.ok && status.env_key_configured && status.base_url) {
|
|
234
|
+
await ensureDir(path.dirname(status.config_path));
|
|
235
|
+
const current = await readText(status.config_path, '');
|
|
236
|
+
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, status.base_url));
|
|
237
|
+
await writeTextAtomic(status.config_path, next);
|
|
238
|
+
configRepaired = true;
|
|
239
|
+
status = await codexLbStatus(opts);
|
|
240
|
+
}
|
|
229
241
|
if (!status.ok) {
|
|
230
242
|
return {
|
|
231
243
|
ok: false,
|
|
232
|
-
status: 'not_configured',
|
|
244
|
+
status: !status.env_key_configured ? 'missing_env_key' : !status.base_url ? 'missing_base_url' : 'not_configured',
|
|
233
245
|
config_path: status.config_path,
|
|
234
246
|
env_path: status.env_path,
|
|
235
247
|
codex_lb: status
|
|
@@ -242,6 +254,7 @@ export async function repairCodexLbAuth(opts = {}) {
|
|
|
242
254
|
config_path: status.config_path,
|
|
243
255
|
env_path: status.env_path,
|
|
244
256
|
base_url: status.base_url,
|
|
257
|
+
config_repaired: configRepaired,
|
|
245
258
|
codex_lb: status,
|
|
246
259
|
codex_login: codexLogin
|
|
247
260
|
};
|
|
@@ -251,7 +264,10 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
|
251
264
|
if (process.env.SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH=1' };
|
|
252
265
|
const status = await codexLbStatus(opts);
|
|
253
266
|
if (!status.selected && !status.provider_configured && !status.env_file) return { status: 'not_configured', codex_lb: status };
|
|
254
|
-
if (!status.ok)
|
|
267
|
+
if (!status.ok) {
|
|
268
|
+
if (status.env_key_configured && status.base_url) return repairCodexLbAuth(opts);
|
|
269
|
+
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 };
|
|
270
|
+
}
|
|
255
271
|
const codexLogin = await ensureCodexLbLoginFromEnv(status, { ...opts, force: true });
|
|
256
272
|
return {
|
|
257
273
|
ok: Boolean(codexLogin.ok),
|
|
@@ -464,9 +480,18 @@ function shellSingleQuote(value) {
|
|
|
464
480
|
}
|
|
465
481
|
|
|
466
482
|
function parseCodexLbEnvKey(text = '') {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
483
|
+
return parseShellEnvValue(text, 'CODEX_LB_API_KEY');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function parseCodexLbEnvBaseUrl(text = '') {
|
|
487
|
+
const value = parseShellEnvValue(text, 'CODEX_LB_BASE_URL');
|
|
488
|
+
return value ? normalizeCodexLbBaseUrl(value) : '';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function parseShellEnvValue(text = '', key = '') {
|
|
492
|
+
const re = new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=\\s*(.+?)\\s*$`, 'm');
|
|
493
|
+
const envMatch = String(text || '').match(re);
|
|
494
|
+
const raw = envMatch?.[1]?.trim() || '';
|
|
470
495
|
if (!raw) return '';
|
|
471
496
|
if (raw.startsWith("'")) return raw.endsWith("'") && raw.length > 1 ? raw.slice(1, -1).replace(/'\\''/g, "'") : '';
|
|
472
497
|
if (raw.startsWith('"')) return raw.endsWith('"') && raw.length > 1 ? raw.slice(1, -1).replace(/\\"/g, '"') : '';
|
|
@@ -898,27 +923,27 @@ export async function selftestCodexLb(tmp) {
|
|
|
898
923
|
timeoutMs: 15000,
|
|
899
924
|
maxOutputBytes: 64 * 1024
|
|
900
925
|
});
|
|
901
|
-
if (codexLbSetup.code !== 0) throw new Error(`selftest
|
|
926
|
+
if (codexLbSetup.code !== 0) throw new Error(`selftest: codex-lb setup exited ${codexLbSetup.code}: ${codexLbSetup.stderr}`);
|
|
902
927
|
const codexLbSetupJson = JSON.parse(codexLbSetup.stdout);
|
|
903
928
|
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
904
929
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
905
930
|
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
906
|
-
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || !codexLbConfig.includes('model_provider = "codex-lb"') || !codexLbConfig.includes('[model_providers.codex-lb]') || !codexLbEnv.includes("CODEX_LB_API_KEY='sk-test'") || !/(\"auth_mode\"\s*:\s*\"apikey\")/.test(codexLbAuth)) throw new Error('selftest
|
|
931
|
+
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || !codexLbConfig.includes('model_provider = "codex-lb"') || !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'") || !/(\"auth_mode\"\s*:\s*\"apikey\")/.test(codexLbAuth)) throw new Error('selftest: codex-lb setup');
|
|
907
932
|
await initProject(codexLbHome, { installScope: 'global', force: true, repair: true });
|
|
908
933
|
const codexLbRepairSetupConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
909
|
-
if (!codexLbRepairSetupConfig.includes('model_provider = "codex-lb"') || !codexLbRepairSetupConfig.includes('[model_providers.codex-lb]') || !codexLbRepairSetupConfig.includes('https://lb.example.test/backend-api/codex') || codexLbRepairSetupConfig.includes('sk-test')) throw new Error('selftest
|
|
934
|
+
if (!codexLbRepairSetupConfig.includes('model_provider = "codex-lb"') || !codexLbRepairSetupConfig.includes('[model_providers.codex-lb]') || !codexLbRepairSetupConfig.includes('https://lb.example.test/backend-api/codex') || codexLbRepairSetupConfig.includes('sk-test')) throw new Error('selftest: init codex-lb');
|
|
910
935
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `${codexLbConfig}\n[mcp_servers.supabase]\nurl = "https://mcp.supabase.com/mcp?project_ref=ref&read_only=true&features=database,docs"\n`);
|
|
911
936
|
const ptmp = path.join(tmp, 'codex-lb-project-config'), prevHome = process.env.HOME;
|
|
912
937
|
try { process.env.HOME = codexLbHome; await initProject(ptmp, { installScope: 'global' }); }
|
|
913
938
|
finally { if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; }
|
|
914
939
|
const pcfg = await safeReadText(path.join(ptmp, '.codex', 'config.toml'));
|
|
915
|
-
if (!pcfg.includes('model_provider = "codex-lb"') || !pcfg.includes('[model_providers.codex-lb]') || !pcfg.includes('[mcp_servers.supabase]') || !pcfg.includes('read_only=true')) throw new Error('selftest
|
|
940
|
+
if (!pcfg.includes('model_provider = "codex-lb"') || !pcfg.includes('[model_providers.codex-lb]') || !pcfg.includes('[mcp_servers.supabase]') || !pcfg.includes('read_only=true')) throw new Error('selftest: project codex-lb');
|
|
916
941
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
917
942
|
const codexLbRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'auth', 'repair', '--json'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
918
|
-
if (codexLbRepair.code !== 0) throw new Error(`selftest
|
|
943
|
+
if (codexLbRepair.code !== 0) throw new Error(`selftest: codex-lb repair exited ${codexLbRepair.code}: ${codexLbRepair.stderr}`);
|
|
919
944
|
const codexLbRepairJson = JSON.parse(codexLbRepair.stdout);
|
|
920
945
|
const codexLbRepairedAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
921
|
-
if (!codexLbRepairJson.ok || codexLbRepairJson.status !== 'repaired' || !codexLbRepairedAuth.includes('"auth_mode":"apikey"') || !codexLbRepairedAuth.includes('sk-test')) throw new Error('selftest
|
|
946
|
+
if (!codexLbRepairJson.ok || codexLbRepairJson.status !== 'repaired' || !codexLbRepairedAuth.includes('"auth_mode":"apikey"') || !codexLbRepairedAuth.includes('sk-test')) throw new Error('selftest: codex-lb repair');
|
|
922
947
|
const codexLbLoginCallsBeforePostinstall = (await safeReadText(path.join(codexLbHome, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
923
948
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
924
949
|
const codexLbPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
@@ -935,10 +960,10 @@ export async function selftestCodexLb(tmp) {
|
|
|
935
960
|
timeoutMs: 15000,
|
|
936
961
|
maxOutputBytes: 128 * 1024
|
|
937
962
|
});
|
|
938
|
-
if (codexLbPostinstall.code !== 0) throw new Error(`selftest
|
|
963
|
+
if (codexLbPostinstall.code !== 0) throw new Error(`selftest: codex-lb postinstall auth preservation exited ${codexLbPostinstall.code}: ${codexLbPostinstall.stderr}`);
|
|
939
964
|
const codexLbPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
940
965
|
const codexLbLoginCallsAfterPostinstall = (await safeReadText(path.join(codexLbHome, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
941
|
-
if (!String(codexLbPostinstall.stdout || '').includes('codex-lb auth: preserved') || !codexLbPostinstallAuth.includes('"auth_mode":"apikey"') || !codexLbPostinstallAuth.includes('sk-test') || codexLbLoginCallsAfterPostinstall <= codexLbLoginCallsBeforePostinstall) throw new Error('selftest
|
|
966
|
+
if (!String(codexLbPostinstall.stdout || '').includes('codex-lb auth: preserved') || !codexLbPostinstallAuth.includes('"auth_mode":"apikey"') || !codexLbPostinstallAuth.includes('sk-test') || codexLbLoginCallsAfterPostinstall <= codexLbLoginCallsBeforePostinstall) throw new Error('selftest: postinstall auth');
|
|
942
967
|
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'];
|
|
943
968
|
const postinstallEnvBefore = Object.fromEntries(postinstallEnvKeys.map((key) => [key, process.env[key]]));
|
|
944
969
|
const codexLbLoginCallsBeforeBootstrap = (await safeReadText(path.join(codexLbHome, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
@@ -971,8 +996,25 @@ export async function selftestCodexLb(tmp) {
|
|
|
971
996
|
const codexLbPostBootstrapAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
972
997
|
const codexLbPostBootstrapConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
973
998
|
const codexLbLoginCallsAfterBootstrap = (await safeReadText(path.join(codexLbHome, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
974
|
-
if (!codexLbPostBootstrapAuth.includes('"auth_mode":"apikey"') || !codexLbPostBootstrapAuth.includes('sk-test') || codexLbLoginCallsAfterBootstrap <= codexLbLoginCallsBeforeBootstrap) throw new Error('selftest
|
|
975
|
-
if (!codexLbPostBootstrapConfig.includes('model_provider = "codex-lb"') || !codexLbPostBootstrapConfig.includes('[model_providers.codex-lb]') || !codexLbPostBootstrapConfig.includes('https://lb.example.test/backend-api/codex') || codexLbPostBootstrapConfig.includes('sk-test')) throw new Error('selftest
|
|
999
|
+
if (!codexLbPostBootstrapAuth.includes('"auth_mode":"apikey"') || !codexLbPostBootstrapAuth.includes('sk-test') || codexLbLoginCallsAfterBootstrap <= codexLbLoginCallsBeforeBootstrap) throw new Error('selftest: postinstall drift auth');
|
|
1000
|
+
if (!codexLbPostBootstrapConfig.includes('model_provider = "codex-lb"') || !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');
|
|
1001
|
+
const doctorProject = tmpdir();
|
|
1002
|
+
await ensureDir(path.join(doctorProject, '.git'));
|
|
1003
|
+
await writeTextAtomic(path.join(doctorProject, 'package.json'), '{"name":"codex-lb-doctor-project","version":"0.0.0"}\n');
|
|
1004
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-test'\n");
|
|
1005
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
1006
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nservice_tier = "fast"\n\n[features]\nhooks = true\n');
|
|
1007
|
+
const codexLbDoctorRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--json'], {
|
|
1008
|
+
cwd: doctorProject,
|
|
1009
|
+
env: { ...codexLbEnvForSelftest, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-doctor-global') },
|
|
1010
|
+
timeoutMs: 30000,
|
|
1011
|
+
maxOutputBytes: 256 * 1024
|
|
1012
|
+
});
|
|
1013
|
+
if (codexLbDoctorRepair.code !== 0) throw new Error(`selftest: doctor --fix codex-lb repair exited ${codexLbDoctorRepair.code}: ${codexLbDoctorRepair.stderr}`);
|
|
1014
|
+
const codexLbDoctorJson = JSON.parse(codexLbDoctorRepair.stdout);
|
|
1015
|
+
const codexLbDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1016
|
+
const codexLbDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1017
|
+
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbDoctorAuth.includes('sk-test') || !codexLbDoctorConfig.includes('model_provider = "codex-lb"') || !codexLbDoctorConfig.includes('https://lb.example.test/backend-api/codex')) throw new Error('selftest: doctor codex-lb');
|
|
976
1018
|
const codexLbContext7Bin = path.join(tmp, 'codex-lb-context7-bin');
|
|
977
1019
|
await ensureDir(codexLbContext7Bin);
|
|
978
1020
|
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');
|
|
@@ -992,7 +1034,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
992
1034
|
timeoutMs: 15000,
|
|
993
1035
|
maxOutputBytes: 128 * 1024
|
|
994
1036
|
});
|
|
995
|
-
if (codexLbContext7Postinstall.code !== 0 || String(`${codexLbContext7Postinstall.stdout}\n${codexLbContext7Postinstall.stderr}`).includes('leaked CODEX_LB_API_KEY')) throw new Error('selftest
|
|
1037
|
+
if (codexLbContext7Postinstall.code !== 0 || String(`${codexLbContext7Postinstall.stdout}\n${codexLbContext7Postinstall.stderr}`).includes('leaked CODEX_LB_API_KEY')) throw new Error('selftest: Context7 key leak');
|
|
996
1038
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_API_KEY='unterminated\n");
|
|
997
1039
|
const codexLbLoginCallsBeforeMalformed = (await safeReadText(path.join(codexLbHome, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
998
1040
|
const codexLbMalformedPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
@@ -1010,7 +1052,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1010
1052
|
maxOutputBytes: 128 * 1024
|
|
1011
1053
|
});
|
|
1012
1054
|
const codexLbLoginCallsAfterMalformed = (await safeReadText(path.join(codexLbHome, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
1013
|
-
if (codexLbMalformedPostinstall.code !== 0 || !String(codexLbMalformedPostinstall.stdout || '').includes('codex-lb auth: stored key missing') || codexLbLoginCallsAfterMalformed !== codexLbLoginCallsBeforeMalformed) throw new Error('selftest
|
|
1055
|
+
if (codexLbMalformedPostinstall.code !== 0 || !String(codexLbMalformedPostinstall.stdout || '').includes('codex-lb auth: stored key missing') || codexLbLoginCallsAfterMalformed !== codexLbLoginCallsBeforeMalformed) throw new Error('selftest: bad codex-lb env');
|
|
1014
1056
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_API_KEY='sk-test'\n");
|
|
1015
1057
|
const codexLbMissingCli = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1016
1058
|
cwd: tmp,
|
|
@@ -1028,7 +1070,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1028
1070
|
timeoutMs: 15000,
|
|
1029
1071
|
maxOutputBytes: 128 * 1024
|
|
1030
1072
|
});
|
|
1031
|
-
if (codexLbMissingCli.code !== 0 || !String(codexLbMissingCli.stdout || '').includes('codex-lb auth: repair skipped (codex_missing')) throw new Error('selftest
|
|
1073
|
+
if (codexLbMissingCli.code !== 0 || !String(codexLbMissingCli.stdout || '').includes('codex-lb auth: repair skipped (codex_missing')) throw new Error('selftest: codex missing');
|
|
1032
1074
|
const codexLbNotConfiguredHome = path.join(tmp, 'codex-lb-not-configured-home');
|
|
1033
1075
|
const codexLbNotConfigured = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1034
1076
|
cwd: tmp,
|
|
@@ -1046,16 +1088,16 @@ export async function selftestCodexLb(tmp) {
|
|
|
1046
1088
|
timeoutMs: 15000,
|
|
1047
1089
|
maxOutputBytes: 128 * 1024
|
|
1048
1090
|
});
|
|
1049
|
-
if (codexLbNotConfigured.code !== 0 || String(codexLbNotConfigured.stdout || '').includes('codex-lb auth:')) throw new Error('selftest
|
|
1091
|
+
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');
|
|
1050
1092
|
const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
1051
|
-
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest
|
|
1052
|
-
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest
|
|
1093
|
+
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest: codex-lb status did not advertise repair command');
|
|
1094
|
+
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, Fast mode defaults, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
|
|
1053
1095
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
1054
|
-
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest
|
|
1055
|
-
if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest
|
|
1056
|
-
if (!codexLbLaunch.includes('SKS_TMUX_LOGO_ANIMATION') || !codexLbLaunch.includes('SNEAKOSCOPE CODEX')) throw new Error('selftest
|
|
1096
|
+
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest: tmux launch command does not source codex-lb env file');
|
|
1097
|
+
if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest: tmux launch command without args did not force GPT-5.5');
|
|
1098
|
+
if (!codexLbLaunch.includes('SKS_TMUX_LOGO_ANIMATION') || !codexLbLaunch.includes('SNEAKOSCOPE CODEX')) throw new Error('selftest: tmux launch command does not include the animated SKS logo intro');
|
|
1057
1099
|
const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', 'maintenance-commands.mjs'));
|
|
1058
|
-
if (!madLaunchSource.includes('const lb = await deps.maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest
|
|
1100
|
+
if (!madLaunchSource.includes('const lb = await deps.maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest: MAD launch does not sync codex-lb auth and fresh-session launch options');
|
|
1059
1101
|
|
|
1060
1102
|
}
|
|
1061
1103
|
|