sneakoscope 0.7.48 → 0.7.49
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 +8 -2
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +29 -4
- package/src/cli/main.mjs +59 -11
- package/src/core/db-safety.mjs +3 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/routes.mjs +2 -1
package/README.md
CHANGED
|
@@ -174,6 +174,7 @@ If you use [codex-lb](https://github.com/Soju06/codex-lb), start it first, creat
|
|
|
174
174
|
|
|
175
175
|
```sh
|
|
176
176
|
sks codex-lb setup --host https://your-codex-lb.example.com --api-key "sk-clb-..."
|
|
177
|
+
sks codex-lb repair
|
|
177
178
|
sks
|
|
178
179
|
```
|
|
179
180
|
|
|
@@ -183,7 +184,11 @@ Bare `sks` asks this before opening Codex when codex-lb is not configured:
|
|
|
183
184
|
Authenticate and route Codex through codex-lb? [y/N]
|
|
184
185
|
```
|
|
185
186
|
|
|
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`, syncs Codex CLI API-key auth through `codex login --with-api-key`, 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. SKS keeps Codex App Fast mode visible and defaulted by writing `service_tier = "fast"`, `[features].fast_mode = true`, and the `sks-fast-high` profile while removing only legacy top-level `model` and `model_reasoning_effort` locks; route-specific reasoning stays in named profiles or explicit tmux launch args.
|
|
187
|
+
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`, syncs Codex CLI API-key auth through `codex login --with-api-key`, 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. SKS keeps Codex App Fast mode visible and defaulted by writing `service_tier = "fast"`, `[features].fast_mode = true`, and the `sks-fast-high` profile while removing only legacy top-level `model` and `model_reasoning_effort` locks; route-specific reasoning stays in named profiles or explicit tmux launch args.
|
|
188
|
+
|
|
189
|
+
If Codex CLI auth drifts after a tmux/MAD launch, run `sks codex-lb repair` or `sks auth repair`. This reuses the stored `~/.codex/sks-codex-lb.env` key and re-syncs Codex CLI API-key auth without asking for the key again. To replace the key or host, run `sks codex-lb reconfigure --host <domain> --api-key <key>`.
|
|
190
|
+
|
|
191
|
+
The generated provider config follows the codex-lb README's Codex CLI API-key setup:
|
|
187
192
|
|
|
188
193
|
```toml
|
|
189
194
|
model_provider = "codex-lb"
|
|
@@ -205,7 +210,7 @@ sks --mad
|
|
|
205
210
|
sks --mad --yes
|
|
206
211
|
```
|
|
207
212
|
|
|
208
|
-
This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches Codex with `--sandbox danger-full-access --ask-for-approval never` and attaches to the session in an interactive terminal. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, and needed migrations are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active. Repeat launches reuse the same named SKS MAD tmux session.
|
|
213
|
+
This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches Codex with `--sandbox danger-full-access --ask-for-approval never` and attaches to the session in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, and needed migrations are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active. Repeat launches reuse the same named SKS MAD tmux session unless auth repair requires a fresh codex-lb session.
|
|
209
214
|
|
|
210
215
|
MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
|
|
211
216
|
|
|
@@ -448,6 +453,7 @@ sks dollar-commands
|
|
|
448
453
|
|
|
449
454
|
```sh
|
|
450
455
|
sks codex-lb setup --host <domain> --api-key <key>
|
|
456
|
+
sks codex-lb repair
|
|
451
457
|
sks
|
|
452
458
|
```
|
|
453
459
|
|
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.49",
|
|
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",
|
|
@@ -148,7 +148,7 @@ export async function configureCodexLb(opts = {}) {
|
|
|
148
148
|
await writeTextAtomic(envPath, `export CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
149
149
|
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
150
150
|
process.env.CODEX_LB_API_KEY = apiKey;
|
|
151
|
-
const codexLogin = await syncCodexApiKeyLogin(apiKey, { home });
|
|
151
|
+
const codexLogin = await syncCodexApiKeyLogin(apiKey, { home, force: true });
|
|
152
152
|
return { ok: true, status: 'configured', config_path: configPath, env_path: envPath, base_url: baseUrl, env_key: 'CODEX_LB_API_KEY', codex_login: codexLogin };
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -174,6 +174,29 @@ export async function codexLbStatus(opts = {}) {
|
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
export async function repairCodexLbAuth(opts = {}) {
|
|
178
|
+
const status = await codexLbStatus(opts);
|
|
179
|
+
if (!status.ok) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
status: 'not_configured',
|
|
183
|
+
config_path: status.config_path,
|
|
184
|
+
env_path: status.env_path,
|
|
185
|
+
codex_lb: status
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const codexLogin = await ensureCodexLbLoginFromEnv(status, opts);
|
|
189
|
+
return {
|
|
190
|
+
ok: Boolean(codexLogin.ok),
|
|
191
|
+
status: codexLogin.ok ? 'repaired' : codexLogin.status,
|
|
192
|
+
config_path: status.config_path,
|
|
193
|
+
env_path: status.env_path,
|
|
194
|
+
base_url: status.base_url,
|
|
195
|
+
codex_lb: status,
|
|
196
|
+
codex_login: codexLogin
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
177
200
|
export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
178
201
|
if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1') return { status: 'skipped' };
|
|
179
202
|
if (!canAskYesNo()) return { status: 'non_interactive' };
|
|
@@ -198,7 +221,7 @@ async function ensureCodexLbLoginFromEnv(status = {}, opts = {}) {
|
|
|
198
221
|
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
199
222
|
const apiKey = parseCodexLbEnvKey(await readText(envPath, ''));
|
|
200
223
|
if (!apiKey) return { ok: false, status: 'missing_env_key' };
|
|
201
|
-
return syncCodexApiKeyLogin(apiKey, { ...opts, home });
|
|
224
|
+
return syncCodexApiKeyLogin(apiKey, { ...opts, home, force: true });
|
|
202
225
|
}
|
|
203
226
|
|
|
204
227
|
async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
@@ -208,8 +231,10 @@ async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
|
208
231
|
if (!codexBin) return { ok: false, status: 'codex_missing' };
|
|
209
232
|
await ensureDir(codexHome);
|
|
210
233
|
const env = { HOME: home, CODEX_HOME: codexHome, CODEX_LB_API_KEY: apiKey };
|
|
211
|
-
|
|
212
|
-
|
|
234
|
+
if (!opts.force) {
|
|
235
|
+
const current = await runProcess(codexBin, ['login', 'status'], { env, timeoutMs: 10000, maxOutputBytes: 8192 });
|
|
236
|
+
if (current.code === 0 && !/not logged in/i.test(`${current.stdout}\n${current.stderr}`)) return { ok: true, status: 'present' };
|
|
237
|
+
}
|
|
213
238
|
const login = await runProcess(codexBin, ['login', '--with-api-key'], { input: `${apiKey}\n`, env, timeoutMs: 15000, maxOutputBytes: 8192 });
|
|
214
239
|
if (login.code === 0) return { ok: true, status: 'synced' };
|
|
215
240
|
return { ok: false, status: 'login_failed', error: (login.stderr || login.stdout || 'codex login failed').trim() };
|
package/src/cli/main.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { containsUserQuestion, noQuestionContinuationReason } from '../core/no-q
|
|
|
14
14
|
import { evaluateDoneGate, defaultDoneGate } from '../core/hproof.mjs';
|
|
15
15
|
import { emitHook } from '../core/hooks-runtime.mjs';
|
|
16
16
|
import { storageReport, enforceRetention, pruneWikiArtifacts } from '../core/retention.mjs';
|
|
17
|
-
import { classifySql, classifyCommand, checkDbOperation, handleMadSksUserConfirmation, loadDbSafetyPolicy, scanDbSafety } from '../core/db-safety.mjs';
|
|
17
|
+
import { classifySql, classifyCommand, classifyToolPayload, checkDbOperation, handleMadSksUserConfirmation, loadDbSafetyPolicy, scanDbSafety } from '../core/db-safety.mjs';
|
|
18
18
|
import { checkHarnessModification, harnessGuardStatus, isHarnessSourceProject } from '../core/harness-guard.mjs';
|
|
19
19
|
import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.mjs';
|
|
20
20
|
import { context7Docs, context7Resolve, context7Text, context7Tools } from '../core/context7-client.mjs';
|
|
@@ -68,7 +68,7 @@ import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs'
|
|
|
68
68
|
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
69
69
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
70
70
|
import { context7Command } from './context7-command.mjs';
|
|
71
|
-
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
71
|
+
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, repairCodexLbAuth, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
72
72
|
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';
|
|
73
73
|
import { openClawCommand } from './openclaw-command.mjs';
|
|
74
74
|
|
|
@@ -98,7 +98,7 @@ export async function main(args) {
|
|
|
98
98
|
if (cmd === 'dollar-commands' || cmd === 'dollars' || cmd === '$') return dollarCommands(tail);
|
|
99
99
|
if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
|
|
100
100
|
const handlers = {
|
|
101
|
-
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),
|
|
101
|
+
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), auth: () => codexLbCommand(sub, rest), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
102
102
|
'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),
|
|
103
103
|
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),
|
|
104
104
|
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)
|
|
@@ -152,7 +152,8 @@ Usage:
|
|
|
152
152
|
sks bootstrap [--install-scope global|project] [--local-only] [--json]
|
|
153
153
|
sks deps check|install [tmux|codex|context7|all] [--yes] [--json]
|
|
154
154
|
sks codex-app
|
|
155
|
-
sks codex-lb setup --host <domain> --api-key <key>
|
|
155
|
+
sks codex-lb status|repair|setup --host <domain> --api-key <key>
|
|
156
|
+
sks auth status|repair|setup --host <domain> --api-key <key>
|
|
156
157
|
sks openclaw install|path|print [--dir path] [--force] [--json]
|
|
157
158
|
sks --mad [--high]
|
|
158
159
|
sks auto-review status|enable|start [--high]
|
|
@@ -1019,14 +1020,29 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1019
1020
|
console.log(`Env file: ${status.env_file ? status.env_path : 'missing'}`);
|
|
1020
1021
|
if (status.base_url) console.log(`Base URL: ${status.base_url}`);
|
|
1021
1022
|
if (!status.ok) console.log('\nRun: sks codex-lb setup --host <domain> --api-key <key>');
|
|
1023
|
+
else console.log('\nRepair auth: sks codex-lb repair');
|
|
1022
1024
|
return;
|
|
1023
1025
|
}
|
|
1024
|
-
if (sub === '
|
|
1026
|
+
if (sub === 'repair' || sub === 'resync' || sub === 'login') {
|
|
1027
|
+
const result = await repairCodexLbAuth();
|
|
1028
|
+
if (json) return console.log(JSON.stringify(result, null, 2));
|
|
1029
|
+
if (!result.ok) {
|
|
1030
|
+
if (result.status === 'not_configured') console.error('codex-lb auth repair failed: codex-lb is not fully configured. Run: sks codex-lb setup --host <domain> --api-key <key>');
|
|
1031
|
+
else console.error(`codex-lb auth repair failed: ${result.status}${result.codex_login?.error ? `: ${result.codex_login.error}` : ''}`);
|
|
1032
|
+
process.exitCode = 1;
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
console.log('codex-lb auth repaired for Codex CLI.');
|
|
1036
|
+
console.log(`Config: ${result.config_path}`);
|
|
1037
|
+
console.log(`Key env: ${result.env_path}`);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (sub === 'setup' || sub === 'reconfigure') {
|
|
1025
1041
|
const host = readOption(args, '--host', readOption(args, '--domain', null));
|
|
1026
1042
|
const apiKey = readOption(args, '--api-key', readOption(args, '--key', null));
|
|
1027
1043
|
if (!host || !apiKey) {
|
|
1028
1044
|
if (json) return console.log(JSON.stringify({ ok: false, reason: 'missing_host_or_api_key' }, null, 2));
|
|
1029
|
-
console.error('Usage: sks codex-lb setup --host <domain> --api-key <key>');
|
|
1045
|
+
console.error('Usage: sks codex-lb setup|reconfigure --host <domain> --api-key <key>');
|
|
1030
1046
|
process.exitCode = 1;
|
|
1031
1047
|
return;
|
|
1032
1048
|
}
|
|
@@ -1042,7 +1058,7 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1042
1058
|
console.log(`Key env: ${result.env_path}`);
|
|
1043
1059
|
return;
|
|
1044
1060
|
}
|
|
1045
|
-
console.error('Usage: sks codex-lb status|setup --host <domain> --api-key <key> [--json]');
|
|
1061
|
+
console.error('Usage: sks codex-lb status|repair|setup --host <domain> --api-key <key> [--json]');
|
|
1046
1062
|
process.exitCode = 1;
|
|
1047
1063
|
}
|
|
1048
1064
|
|
|
@@ -1085,12 +1101,24 @@ async function madHighCommand(args = []) {
|
|
|
1085
1101
|
process.exitCode = 1;
|
|
1086
1102
|
return;
|
|
1087
1103
|
}
|
|
1104
|
+
const lb = await maybePromptCodexLbSetupForLaunch(args);
|
|
1105
|
+
if (lb.status === 'missing_api_key') {
|
|
1106
|
+
process.exitCode = 1;
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1088
1109
|
const profile = await enableMadHighProfile();
|
|
1089
1110
|
const madLaunch = await activateMadTmuxPermissionState(process.cwd());
|
|
1090
1111
|
console.log(`SKS MAD ready: ${madHighProfileName()} | gate ${madLaunch.mission_id}`);
|
|
1091
1112
|
console.log('Live full-access active; catastrophic DB wipe/all-row/project-management guards remain.');
|
|
1092
|
-
const
|
|
1113
|
+
const launchLb = lb.status === 'present' ? { ...lb, status: 'configured' } : lb;
|
|
1114
|
+
const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, {
|
|
1115
|
+
codexArgs: profile.launch_args,
|
|
1116
|
+
autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
|
|
1117
|
+
conciseBlockers: true
|
|
1118
|
+
});
|
|
1119
|
+
const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${defaultTmuxSessionName(process.cwd())}`));
|
|
1093
1120
|
return launchTmuxUi([...cleanArgs, '--workspace', workspace], {
|
|
1121
|
+
...launchOpts,
|
|
1094
1122
|
codexArgs: profile.launch_args,
|
|
1095
1123
|
autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
|
|
1096
1124
|
conciseBlockers: true
|
|
@@ -1438,7 +1466,7 @@ function usage(args = []) {
|
|
|
1438
1466
|
const topic = String(args[0] || 'overview').toLowerCase();
|
|
1439
1467
|
const blocks = {
|
|
1440
1468
|
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}`],
|
|
1441
|
-
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'],
|
|
1469
|
+
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 codex-lb repair', ' sks', '', 'Fallback:', ' npx -y -p sneakoscope sks root', '', 'Project:', ' npm i -D sneakoscope', ' npx sks setup --install-scope project'],
|
|
1442
1470
|
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.'],
|
|
1443
1471
|
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.'],
|
|
1444
1472
|
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.'],
|
|
@@ -2151,10 +2179,16 @@ async function selftest() {
|
|
|
2151
2179
|
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');
|
|
2152
2180
|
const codexLbHome = path.join(tmp, 'codex-lb-home');
|
|
2153
2181
|
await ensureDir(path.join(codexLbHome, '.codex'));
|
|
2182
|
+
const codexLbFakeBin = path.join(tmp, 'codex-lb-fake-bin');
|
|
2183
|
+
await ensureDir(codexLbFakeBin);
|
|
2184
|
+
const codexLbFakeCodex = path.join(codexLbFakeBin, 'codex');
|
|
2185
|
+
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");
|
|
2186
|
+
await fsp.chmod(codexLbFakeCodex, 0o755);
|
|
2154
2187
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "high"\nservice_tier = "fast"\n\n[notice]\nfast_default_opt_out = true\n');
|
|
2188
|
+
const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}` };
|
|
2155
2189
|
const codexLbSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'setup', '--host', 'lb.example.test', '--api-key', 'sk-test', '--json'], {
|
|
2156
2190
|
cwd: tmp,
|
|
2157
|
-
env:
|
|
2191
|
+
env: codexLbEnvForSelftest,
|
|
2158
2192
|
timeoutMs: 15000,
|
|
2159
2193
|
maxOutputBytes: 64 * 1024
|
|
2160
2194
|
});
|
|
@@ -2163,11 +2197,21 @@ async function selftest() {
|
|
|
2163
2197
|
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
2164
2198
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
2165
2199
|
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
2166
|
-
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'") ||
|
|
2200
|
+
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 failed: codex-lb setup did not write provider config, env key, and Codex API-key auth');
|
|
2201
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
2202
|
+
const codexLbRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'auth', 'repair', '--json'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2203
|
+
if (codexLbRepair.code !== 0) throw new Error(`selftest failed: codex-lb repair exited ${codexLbRepair.code}: ${codexLbRepair.stderr}`);
|
|
2204
|
+
const codexLbRepairJson = JSON.parse(codexLbRepair.stdout);
|
|
2205
|
+
const codexLbRepairedAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
2206
|
+
if (!codexLbRepairJson.ok || codexLbRepairJson.status !== 'repaired' || !codexLbRepairedAuth.includes('"auth_mode":"apikey"') || !codexLbRepairedAuth.includes('sk-test')) throw new Error('selftest failed: codex-lb repair did not force API-key auth from stored env key');
|
|
2207
|
+
const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2208
|
+
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest failed: codex-lb status did not advertise repair command');
|
|
2167
2209
|
if (!codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = 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 failed: codex-lb setup did not preserve Codex App Fast mode defaults');
|
|
2168
2210
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
2169
2211
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
|
|
2170
2212
|
if (!codexLbLaunch.includes('SKS_TMUX_LOGO_ANIMATION') || !codexLbLaunch.includes('SNEAKOSCOPE CODEX')) throw new Error('selftest failed: tmux launch command does not include the animated SKS logo intro');
|
|
2213
|
+
const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', 'main.mjs'));
|
|
2214
|
+
if (!madLaunchSource.includes('const lb = await maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest failed: MAD launch does not sync codex-lb auth and fresh-session launch options');
|
|
2171
2215
|
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');
|
|
2172
2216
|
if (shouldAutoAttachTmux(['--mad', '--json'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux json mode should not auto-attach');
|
|
2173
2217
|
if (shouldAutoAttachTmux(['--mad', '--no-attach'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux --no-attach should remain print-only');
|
|
@@ -3310,6 +3354,10 @@ async function selftest() {
|
|
|
3310
3354
|
await setCurrent(tmp, { mission_id: id, mode: 'QALOOP', phase: 'QALOOP_RUNNING_NO_QUESTIONS' });
|
|
3311
3355
|
if (!containsUserQuestion('확인해 주세요?')) throw new Error('selftest failed: question guard');
|
|
3312
3356
|
if (classifySql('drop table users;').level !== 'destructive') throw new Error('selftest failed: destructive sql not detected');
|
|
3357
|
+
const patchPayloadClass = classifyToolPayload({ tool_name: 'apply_patch', command: '*** Update File: src/example.mjs\n+ok\n' });
|
|
3358
|
+
if (patchPayloadClass.level !== 'none') throw new Error('selftest failed: apply_patch file edits should not be classified as DB writes');
|
|
3359
|
+
const supabaseWritePayloadClass = classifyToolPayload({ tool_name: 'mcp__supabase__execute_sql', sql: "update users set name = 'x' where id = '1';" });
|
|
3360
|
+
if (supabaseWritePayloadClass.level !== 'write' || !supabaseWritePayloadClass.toolReasons.includes('database_tool')) throw new Error('selftest failed: Supabase execute_sql write classification was weakened');
|
|
3313
3361
|
if (classifyCommand('supabase db reset').level !== 'destructive') throw new Error('selftest failed: supabase db reset not detected');
|
|
3314
3362
|
const dbDecision = await checkDbOperation(tmp, { mission_id: id }, { tool_name: 'mcp__supabase__execute_sql', sql: 'drop table users;' }, { duringNoQuestion: true });
|
|
3315
3363
|
if (dbDecision.action !== 'block') throw new Error('selftest failed: destructive MCP SQL allowed');
|
package/src/core/db-safety.mjs
CHANGED
|
@@ -199,6 +199,9 @@ export function classifyToolPayload(payload = {}) {
|
|
|
199
199
|
const sqlClass = classifySql(combined);
|
|
200
200
|
const commandClass = classifyCommand(strings.find((s) => /\b(supabase|psql|prisma|drizzle|knex|sequelize)\b/i.test(s)) || '');
|
|
201
201
|
const toolReasons = [];
|
|
202
|
+
if (/\b(apply_patch|edit|write|create|remove|rename|str_replace|file_write|fs_write)\b/i.test(toolName) && !/supabase|postgres|database|execute_sql|apply_migration|sql_query|db_|_db\b|migration/.test(toolName)) {
|
|
203
|
+
return { level: 'none', toolName, toolReasons, sql: sqlClass, command: commandClass, stringsExamined: strings.length };
|
|
204
|
+
}
|
|
202
205
|
if (/supabase|postgres|database|execute_sql|apply_migration|sql_query|db_|_db\b|migration/.test(toolName)) toolReasons.push('database_tool');
|
|
203
206
|
if (/delete_project|pause_project|restore_project|delete_branch|reset_branch|merge_branch/.test(toolName)) toolReasons.push('dangerous_supabase_management_tool');
|
|
204
207
|
let level = 'none';
|
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.49';
|
|
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,7 +456,8 @@ 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|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.' },
|
|
459
|
-
{ name: 'codex-lb', usage: 'sks codex-lb status|setup --host <domain> --api-key <key>', description: 'Configure codex-lb
|
|
459
|
+
{ name: 'codex-lb', usage: 'sks codex-lb status|repair|setup --host <domain> --api-key <key>', description: 'Configure or repair codex-lb Codex CLI auth by writing ~/.codex/config.toml, syncing auth.json, and loading the CODEX_LB_API_KEY env file.' },
|
|
460
|
+
{ name: 'auth', usage: 'sks auth status|repair|setup --host <domain> --api-key <key>', description: 'Shortcut for codex-lb auth status, repair, and setup commands.' },
|
|
460
461
|
{ 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.' },
|
|
461
462
|
{ 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.' },
|
|
462
463
|
{ name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot tmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
|