sneakoscope 0.7.40 → 0.7.41
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 +15 -9
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +127 -1
- package/src/cli/main.mjs +81 -6
- package/src/core/fsx.mjs +1 -1
- package/src/core/routes.mjs +1 -0
- package/src/core/tmux-ui.mjs +1 -0
package/README.md
CHANGED
|
@@ -170,7 +170,20 @@ Bare `sks` creates or reuses the default named tmux session for Codex CLI and at
|
|
|
170
170
|
|
|
171
171
|
Before opening tmux, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens tmux with the updated Codex CLI.
|
|
172
172
|
|
|
173
|
-
If you use [codex-lb](https://github.com/Soju06/codex-lb), start it first, create an API key in its dashboard, then
|
|
173
|
+
If you use [codex-lb](https://github.com/Soju06/codex-lb), start it first, create an API key in its dashboard, then run:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
sks codex-lb setup --host https://your-codex-lb.example.com --api-key "sk-clb-..."
|
|
177
|
+
sks
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Bare `sks` asks this before opening Codex when codex-lb is not configured:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
Authenticate and route Codex through codex-lb? [y/N]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Answering `y` asks for the hosted domain and API key, writes `~/.codex/config.toml`, stores the key in `~/.codex/sks-codex-lb.env` with mode `0600`, and sources that env file before launching Codex in tmux. When codex-lb is configured from this prompt, SKS opens a fresh tmux session for that launch so the new key is loaded by the Codex process immediately. The generated provider config follows the codex-lb README's Codex CLI API-key setup:
|
|
174
187
|
|
|
175
188
|
```toml
|
|
176
189
|
model_provider = "codex-lb"
|
|
@@ -184,13 +197,6 @@ supports_websockets = true
|
|
|
184
197
|
requires_openai_auth = true
|
|
185
198
|
```
|
|
186
199
|
|
|
187
|
-
Then run:
|
|
188
|
-
|
|
189
|
-
```sh
|
|
190
|
-
export CODEX_LB_API_KEY="sk-clb-..."
|
|
191
|
-
sks
|
|
192
|
-
```
|
|
193
|
-
|
|
194
200
|
### MAD tmux Launch
|
|
195
201
|
|
|
196
202
|
```sh
|
|
@@ -431,7 +437,7 @@ sks dollar-commands
|
|
|
431
437
|
4. Optional codex-lb key setup for CLI `sks` runs.
|
|
432
438
|
|
|
433
439
|
```sh
|
|
434
|
-
|
|
440
|
+
sks codex-lb setup --host <domain> --api-key <key>
|
|
435
441
|
sks
|
|
436
442
|
```
|
|
437
443
|
|
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.41",
|
|
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, runProcess, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
6
|
+
import { ensureDir, exists, globalSksRoot, packageRoot, readText, runProcess, 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 { installSkills } from '../core/init.mjs';
|
|
@@ -113,6 +113,132 @@ export async function askPostinstallQuestion(question) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
export function codexLbConfigPath(home = process.env.HOME || os.homedir()) {
|
|
117
|
+
return path.join(home, '.codex', 'config.toml');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function codexLbEnvPath(home = process.env.HOME || os.homedir()) {
|
|
121
|
+
return path.join(home, '.codex', 'sks-codex-lb.env');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function normalizeCodexLbBaseUrl(input = '') {
|
|
125
|
+
let host = String(input || '').trim();
|
|
126
|
+
if (!host) host = 'http://127.0.0.1:2455';
|
|
127
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(host)) host = `https://${host}`;
|
|
128
|
+
host = host.replace(/\/+$/, '');
|
|
129
|
+
return /\/backend-api\/codex$/i.test(host) ? host : `${host}/backend-api/codex`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function configureCodexLb(opts = {}) {
|
|
133
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
134
|
+
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
135
|
+
const envPath = opts.envPath || codexLbEnvPath(home);
|
|
136
|
+
const baseUrl = normalizeCodexLbBaseUrl(opts.host || opts.baseUrl);
|
|
137
|
+
const apiKey = String(opts.apiKey || '').trim();
|
|
138
|
+
if (!apiKey) return { ok: false, status: 'missing_api_key', config_path: configPath, env_path: envPath };
|
|
139
|
+
await ensureDir(path.dirname(configPath));
|
|
140
|
+
const current = await readText(configPath, '');
|
|
141
|
+
const next = upsertCodexLbConfig(current, baseUrl);
|
|
142
|
+
await writeTextAtomic(configPath, next);
|
|
143
|
+
await writeTextAtomic(envPath, `export CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
144
|
+
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
145
|
+
process.env.CODEX_LB_API_KEY = apiKey;
|
|
146
|
+
return { ok: true, status: 'configured', config_path: configPath, env_path: envPath, base_url: baseUrl, env_key: 'CODEX_LB_API_KEY' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function codexLbStatus(opts = {}) {
|
|
150
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
151
|
+
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
152
|
+
const envPath = opts.envPath || codexLbEnvPath(home);
|
|
153
|
+
const config = await readText(configPath, '');
|
|
154
|
+
const envExists = await exists(envPath);
|
|
155
|
+
const envText = envExists ? await readText(envPath, '') : '';
|
|
156
|
+
const envKeyConfigured = /^(\s*export\s+)?CODEX_LB_API_KEY\s*=.+$/m.test(envText);
|
|
157
|
+
const providerConfigured = /\[model_providers\.codex-lb\]/.test(config);
|
|
158
|
+
const selected = /model_provider\s*=\s*"codex-lb"/.test(config);
|
|
159
|
+
return {
|
|
160
|
+
ok: selected && providerConfigured && envKeyConfigured,
|
|
161
|
+
config_path: configPath,
|
|
162
|
+
env_path: envPath,
|
|
163
|
+
provider_configured: providerConfigured,
|
|
164
|
+
selected,
|
|
165
|
+
env_file: envExists,
|
|
166
|
+
env_key_configured: envKeyConfigured,
|
|
167
|
+
base_url: config.match(/base_url\s*=\s*"([^"]+)"/)?.[1] || null
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
172
|
+
if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1') return { status: 'skipped' };
|
|
173
|
+
if (!canAskYesNo()) return { status: 'non_interactive' };
|
|
174
|
+
const status = await codexLbStatus(opts);
|
|
175
|
+
if (status.ok) return { status: 'present', ...status };
|
|
176
|
+
const useCodexLb = (await askPostinstallQuestion('\nAuthenticate and route Codex through codex-lb? [y/N] ')).trim();
|
|
177
|
+
if (!/^(y|yes|예|네|응)$/i.test(useCodexLb)) return { status: 'continued_to_codex' };
|
|
178
|
+
const host = (await askPostinstallQuestion('codex-lb host domain [http://127.0.0.1:2455]: ')).trim() || 'http://127.0.0.1:2455';
|
|
179
|
+
const apiKey = (await askPostinstallQuestion('codex-lb API key: ')).trim();
|
|
180
|
+
const configured = await configureCodexLb({ ...opts, host, apiKey });
|
|
181
|
+
if (configured.ok) console.log(`codex-lb configured: ${configured.base_url}`);
|
|
182
|
+
else console.log('codex-lb setup skipped: API key was empty.');
|
|
183
|
+
return configured;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function upsertCodexLbConfig(text = '', baseUrl) {
|
|
187
|
+
let next = upsertTopLevelTomlString(text, 'model_provider', 'codex-lb');
|
|
188
|
+
const block = [
|
|
189
|
+
'[model_providers.codex-lb]',
|
|
190
|
+
'name = "OpenAI"',
|
|
191
|
+
`base_url = "${baseUrl}"`,
|
|
192
|
+
'wire_api = "responses"',
|
|
193
|
+
'env_key = "CODEX_LB_API_KEY"',
|
|
194
|
+
'supports_websockets = true',
|
|
195
|
+
'requires_openai_auth = true'
|
|
196
|
+
].join('\n');
|
|
197
|
+
next = upsertTomlTable(next, 'model_providers.codex-lb', block);
|
|
198
|
+
return `${next.trim()}\n`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function upsertTopLevelTomlString(text, key, value) {
|
|
202
|
+
const line = `${key} = "${value}"`;
|
|
203
|
+
const lines = String(text || '').split('\n');
|
|
204
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
205
|
+
const end = firstTable === -1 ? lines.length : firstTable;
|
|
206
|
+
for (let i = 0; i < end; i++) {
|
|
207
|
+
if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[i])) {
|
|
208
|
+
lines[i] = line;
|
|
209
|
+
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
lines.splice(end, 0, line);
|
|
213
|
+
return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function upsertTomlTable(text, table, block) {
|
|
217
|
+
let lines = String(text || '').trimEnd().split('\n');
|
|
218
|
+
if (lines.length === 1 && lines[0] === '') lines = [];
|
|
219
|
+
const header = `[${table}]`;
|
|
220
|
+
const start = lines.findIndex((x) => x.trim() === header);
|
|
221
|
+
const blockLines = String(block || '').trim().split('\n');
|
|
222
|
+
if (start === -1) return [...lines, ...(lines.length ? [''] : []), ...blockLines].join('\n').replace(/\n{3,}/g, '\n\n');
|
|
223
|
+
let end = lines.length;
|
|
224
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
225
|
+
if (/^\s*\[.+\]\s*$/.test(lines[i])) {
|
|
226
|
+
end = i;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
lines.splice(start, end - start, ...blockLines);
|
|
231
|
+
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function shellSingleQuote(value) {
|
|
235
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function escapeRegExp(value) {
|
|
239
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
240
|
+
}
|
|
241
|
+
|
|
116
242
|
export async function ensureSksCommandDuringInstall(opts = {}) {
|
|
117
243
|
if (process.env.SKS_SKIP_POSTINSTALL_SHIM === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_SHIM=1' };
|
|
118
244
|
const pathEnv = opts.pathEnv ?? process.env.PATH ?? '';
|
package/src/cli/main.mjs
CHANGED
|
@@ -58,10 +58,10 @@ import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-
|
|
|
58
58
|
import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
59
59
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
60
60
|
import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
61
|
-
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
61
|
+
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
62
62
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
63
63
|
import { context7Command } from './context7-command.mjs';
|
|
64
|
-
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
64
|
+
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
65
65
|
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
66
66
|
import { openClawCommand } from './openclaw-command.mjs';
|
|
67
67
|
|
|
@@ -91,7 +91,7 @@ export async function main(args) {
|
|
|
91
91
|
if (cmd === 'dollar-commands' || cmd === 'dollars' || cmd === '$') return dollarCommands(tail);
|
|
92
92
|
if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
|
|
93
93
|
const handlers = {
|
|
94
|
-
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
94
|
+
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), 'codex-lb': () => codexLbCommand(sub, rest), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
95
95
|
'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
|
|
96
96
|
goal: () => goalCommand(sub, rest), research: () => researchCommand(sub, rest), hook: () => emitHook(sub), profile: () => profileCommand(sub, rest), hproof: () => hproofCommand(sub, rest), 'validate-artifacts': () => validateArtifactsCommand(tail), perf: () => perfCommand(sub, rest), 'proof-field': () => proofFieldCommand(sub, rest), 'skill-dream': () => skillDreamCommand(sub, rest), 'code-structure': () => codeStructureCommand(sub, rest), memory: () => memoryCommand(sub, rest), gx: () => gxCommand(sub, rest),
|
|
97
97
|
team: () => team(tail), db: () => dbCommand(sub, rest), eval: () => evalCommand(sub, rest), harness: () => harnessCommand(sub, rest), wiki: () => wikiCommand(sub, rest), gc: () => gcCommand(tail), stats: () => statsCommand(tail)
|
|
@@ -118,7 +118,12 @@ async function defaultTmuxCommand(args = []) {
|
|
|
118
118
|
process.exitCode = 1;
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
|
-
|
|
121
|
+
const lb = await maybePromptCodexLbSetupForLaunch(args);
|
|
122
|
+
if (lb.status === 'missing_api_key') {
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
return launchTmuxUi(args, codexLbImmediateLaunchOpts(args, lb, { conciseBlockers: true }));
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
function help(args = []) {
|
|
@@ -140,6 +145,7 @@ Usage:
|
|
|
140
145
|
sks bootstrap [--install-scope global|project] [--local-only] [--json]
|
|
141
146
|
sks deps check|install [tmux|codex|context7|all] [--yes] [--json]
|
|
142
147
|
sks codex-app
|
|
148
|
+
sks codex-lb setup --host <domain> --api-key <key>
|
|
143
149
|
sks openclaw install|path|print [--dir path] [--force] [--json]
|
|
144
150
|
sks --mad [--high]
|
|
145
151
|
sks auto-review status|enable|start [--high]
|
|
@@ -906,7 +912,12 @@ async function tmuxCommand(sub = 'start', args = []) {
|
|
|
906
912
|
process.exitCode = 1;
|
|
907
913
|
return;
|
|
908
914
|
}
|
|
909
|
-
const
|
|
915
|
+
const lb = await maybePromptCodexLbSetupForLaunch(args);
|
|
916
|
+
if (lb.status === 'missing_api_key') {
|
|
917
|
+
process.exitCode = 1;
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const result = await launchTmuxUi(args, codexLbImmediateLaunchOpts(args, lb));
|
|
910
921
|
if (flag(args, '--json')) console.log(JSON.stringify(result, null, 2));
|
|
911
922
|
return;
|
|
912
923
|
}
|
|
@@ -914,6 +925,56 @@ async function tmuxCommand(sub = 'start', args = []) {
|
|
|
914
925
|
process.exitCode = 1;
|
|
915
926
|
}
|
|
916
927
|
|
|
928
|
+
async function codexLbCommand(action = 'status', args = []) {
|
|
929
|
+
const sub = action || 'status';
|
|
930
|
+
const json = flag(args, '--json');
|
|
931
|
+
if (sub === 'status' || sub === 'check') {
|
|
932
|
+
const status = await codexLbStatus();
|
|
933
|
+
if (json) return console.log(JSON.stringify(status, null, 2));
|
|
934
|
+
console.log('SKS codex-lb\n');
|
|
935
|
+
console.log(`Configured: ${status.ok ? 'yes' : 'no'}`);
|
|
936
|
+
console.log(`Selected: ${status.selected ? 'yes' : 'no'}`);
|
|
937
|
+
console.log(`Provider: ${status.provider_configured ? 'yes' : 'no'}`);
|
|
938
|
+
console.log(`Env file: ${status.env_file ? status.env_path : 'missing'}`);
|
|
939
|
+
if (status.base_url) console.log(`Base URL: ${status.base_url}`);
|
|
940
|
+
if (!status.ok) console.log('\nRun: sks codex-lb setup --host <domain> --api-key <key>');
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (sub === 'setup') {
|
|
944
|
+
const host = readOption(args, '--host', readOption(args, '--domain', null));
|
|
945
|
+
const apiKey = readOption(args, '--api-key', readOption(args, '--key', null));
|
|
946
|
+
if (!host || !apiKey) {
|
|
947
|
+
if (json) return console.log(JSON.stringify({ ok: false, reason: 'missing_host_or_api_key' }, null, 2));
|
|
948
|
+
console.error('Usage: sks codex-lb setup --host <domain> --api-key <key>');
|
|
949
|
+
process.exitCode = 1;
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const result = await configureCodexLb({ host, apiKey });
|
|
953
|
+
if (json) return console.log(JSON.stringify(result, null, 2));
|
|
954
|
+
if (!result.ok) {
|
|
955
|
+
console.error(`codex-lb setup failed: ${result.status}`);
|
|
956
|
+
process.exitCode = 1;
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
console.log(`codex-lb configured: ${result.base_url}`);
|
|
960
|
+
console.log(`Config: ${result.config_path}`);
|
|
961
|
+
console.log(`Key env: ${result.env_path}`);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
console.error('Usage: sks codex-lb status|setup --host <domain> --api-key <key> [--json]');
|
|
965
|
+
process.exitCode = 1;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
969
|
+
if (!lb?.ok || lb.status !== 'configured') return opts;
|
|
970
|
+
if (readOption(args, '--session', null) || readOption(args, '--workspace', null)) return opts;
|
|
971
|
+
const root = readOption(args, '--root', process.cwd());
|
|
972
|
+
const session = sanitizeTmuxSessionName(`sks-codex-lb-${Date.now().toString(36)}-${defaultTmuxSessionName(root)}`);
|
|
973
|
+
console.log(`codex-lb key loaded for this launch: ${lb.env_path}`);
|
|
974
|
+
console.log(`Using fresh tmux session: ${session}`);
|
|
975
|
+
return { ...opts, session };
|
|
976
|
+
}
|
|
977
|
+
|
|
917
978
|
async function madHighCommand(args = []) {
|
|
918
979
|
const cleanArgs = args.filter((arg) => !['--mad', '--MAD', '--mad-sks', '--high', '--no-auto-install-tmux'].includes(arg));
|
|
919
980
|
if (flag(args, '--json')) {
|
|
@@ -1233,7 +1294,7 @@ function usage(args = []) {
|
|
|
1233
1294
|
const topic = String(args[0] || 'overview').toLowerCase();
|
|
1234
1295
|
const blocks = {
|
|
1235
1296
|
overview: ['ㅅㅋㅅ Usage', '', 'Discover:', ' sks commands', ' sks quickstart', ' sks root', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks tmux check', ' sks dollar-commands', '', `Topics: ${USAGE_TOPICS}`],
|
|
1236
|
-
install: ['Install', '', '1. Global install:', ' npm i -g sneakoscope', '', '2. Bootstrap and check dependencies:', ' sks bootstrap', ' sks deps check', '', '3. Confirm Codex App commands:', ' sks codex-app check', ' sks dollar-commands', '', '4. Optional codex-lb key setup for CLI sks runs:', '
|
|
1297
|
+
install: ['Install', '', '1. Global install:', ' npm i -g sneakoscope', '', '2. Bootstrap and check dependencies:', ' sks bootstrap', ' sks deps check', '', '3. Confirm Codex App commands:', ' sks codex-app check', ' sks dollar-commands', '', '4. Optional codex-lb key setup for CLI sks runs:', ' sks codex-lb setup --host <domain> --api-key <key>', ' sks', '', 'Fallback:', ' npx -y -p sneakoscope sks root', '', 'Project:', ' npm i -D sneakoscope', ' npx sks setup --install-scope project'],
|
|
1237
1298
|
bootstrap: ['Bootstrap', '', ' sks bootstrap', ' sks setup --bootstrap', '', 'Creates project SKS files, Codex App skills/hooks/config, state/guard files, then checks Codex App, Context7, and tmux.'],
|
|
1238
1299
|
root: ['Root', '', ' sks root [--json]', '', 'Inside a project, SKS uses that project root. Outside any project marker, runtime commands use the per-user global SKS root instead of writing .sneakoscope into the current random folder.'],
|
|
1239
1300
|
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew after Y/n approval for missing installs or Homebrew-managed upgrades. If PATH resolves an npm-managed tmux, SKS prompts for npm i -g tmux@latest instead. Unknown non-Homebrew tmux paths are reported as conflicts.'],
|
|
@@ -1935,6 +1996,20 @@ async function selftest() {
|
|
|
1935
1996
|
if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest failed: MAD tmux attach args are not stable by session name');
|
|
1936
1997
|
const defaultFastHighPlan = await buildTmuxLaunchPlan({ root: tmp, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
1937
1998
|
if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c model_reasoning_effort="high"') throw new Error('selftest failed: default sks tmux launch is not fast-high');
|
|
1999
|
+
const codexLbHome = path.join(tmp, 'codex-lb-home');
|
|
2000
|
+
const codexLbSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'setup', '--host', 'lb.example.test', '--api-key', 'sk-test', '--json'], {
|
|
2001
|
+
cwd: tmp,
|
|
2002
|
+
env: { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global') },
|
|
2003
|
+
timeoutMs: 15000,
|
|
2004
|
+
maxOutputBytes: 64 * 1024
|
|
2005
|
+
});
|
|
2006
|
+
if (codexLbSetup.code !== 0) throw new Error(`selftest failed: codex-lb setup exited ${codexLbSetup.code}: ${codexLbSetup.stderr}`);
|
|
2007
|
+
const codexLbSetupJson = JSON.parse(codexLbSetup.stdout);
|
|
2008
|
+
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
2009
|
+
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
2010
|
+
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'")) throw new Error('selftest failed: codex-lb setup did not write provider config and env key');
|
|
2011
|
+
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
2012
|
+
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
|
|
1938
2013
|
if (!shouldAutoAttachTmux(['--mad'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux launch does not auto-attach in an interactive terminal');
|
|
1939
2014
|
if (shouldAutoAttachTmux(['--mad', '--json'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux json mode should not auto-attach');
|
|
1940
2015
|
if (shouldAutoAttachTmux(['--mad', '--no-attach'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux --no-attach should remain print-only');
|
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.7.
|
|
8
|
+
export const PACKAGE_VERSION = '0.7.41';
|
|
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
|
@@ -456,6 +456,7 @@ export const COMMAND_CATALOG = [
|
|
|
456
456
|
{ name: 'root', usage: 'sks root [--json]', description: 'Show whether SKS is using a project root or the per-user global SKS runtime root.' },
|
|
457
457
|
{ 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 Use, Computer Use, tmux, and Homebrew on macOS.' },
|
|
458
458
|
{ name: 'codex-app', usage: 'sks codex-app [check|open]', description: 'Check Codex App install and first-party MCP/plugin readiness, then show app setup files and examples.' },
|
|
459
|
+
{ name: 'codex-lb', usage: 'sks codex-lb status|setup --host <domain> --api-key <key>', description: 'Configure codex-lb as the Codex CLI provider by writing ~/.codex/config.toml and the CODEX_LB_API_KEY env file.' },
|
|
459
460
|
{ 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.' },
|
|
460
461
|
{ 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.' },
|
|
461
462
|
{ name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot tmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
|
package/src/core/tmux-ui.mjs
CHANGED
|
@@ -108,6 +108,7 @@ export function codexLaunchCommand(root, codexBin, codexArgs = []) {
|
|
|
108
108
|
`printf '\\nProject: %s\\n' ${shellEscape(root)}`,
|
|
109
109
|
'printf \'Runtime: tmux session for Codex CLI\\n\'',
|
|
110
110
|
'printf \'Prompt: use canonical $ commands, for example $Team or $QA-LOOP\\n\\n\'',
|
|
111
|
+
'[ -f "$HOME/.codex/sks-codex-lb.env" ] && . "$HOME/.codex/sks-codex-lb.env"',
|
|
111
112
|
'sleep 1',
|
|
112
113
|
`exec ${[shellEscape(codexBin), ...extraArgs.map(shellEscape), '--cd', shellEscape(root)].join(' ')}`
|
|
113
114
|
].join('; ');
|