sneakoscope 0.7.41 → 0.7.42
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 +2 -2
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +38 -2
- package/src/cli/main.mjs +3 -1
- package/src/core/fsx.mjs +1 -1
- package/src/core/tmux-ui.mjs +43 -8
package/README.md
CHANGED
|
@@ -166,7 +166,7 @@ sks tmux check
|
|
|
166
166
|
sks tmux status --once
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in the SKS fast-high runtime (`--model gpt-5.5 -c model_reasoning_effort="high"`). Override with `SKS_CODEX_MODEL`, `SKS_CODEX_REASONING`,
|
|
169
|
+
Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. By default it launches Codex in the SKS fast-high runtime (`--model gpt-5.5 -c model_reasoning_effort="high"`) with a short animated SKS ASCII intro. Override with `SKS_CODEX_MODEL`, `SKS_CODEX_REASONING`, disable the default model profile with `SKS_CODEX_FAST_HIGH=0`, or disable the intro animation with `SKS_TMUX_LOGO_ANIMATION=0`. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
|
|
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
|
|
|
@@ -183,7 +183,7 @@ Bare `sks` asks this before opening Codex when codex-lb is not configured:
|
|
|
183
183
|
Authenticate and route Codex through codex-lb? [y/N]
|
|
184
184
|
```
|
|
185
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:
|
|
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. The generated provider config follows the codex-lb README's Codex CLI API-key setup:
|
|
187
187
|
|
|
188
188
|
```toml
|
|
189
189
|
model_provider = "codex-lb"
|
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.42",
|
|
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",
|
|
@@ -143,7 +143,8 @@ export async function configureCodexLb(opts = {}) {
|
|
|
143
143
|
await writeTextAtomic(envPath, `export CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
144
144
|
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
145
145
|
process.env.CODEX_LB_API_KEY = apiKey;
|
|
146
|
-
|
|
146
|
+
const codexLogin = await syncCodexApiKeyLogin(apiKey, { home });
|
|
147
|
+
return { ok: true, status: 'configured', config_path: configPath, env_path: envPath, base_url: baseUrl, env_key: 'CODEX_LB_API_KEY', codex_login: codexLogin };
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
export async function codexLbStatus(opts = {}) {
|
|
@@ -172,7 +173,11 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
172
173
|
if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1') return { status: 'skipped' };
|
|
173
174
|
if (!canAskYesNo()) return { status: 'non_interactive' };
|
|
174
175
|
const status = await codexLbStatus(opts);
|
|
175
|
-
if (status.ok)
|
|
176
|
+
if (status.ok) {
|
|
177
|
+
const codexLogin = await ensureCodexLbLoginFromEnv(status, opts);
|
|
178
|
+
if (codexLogin.status === 'synced') console.log('codex-lb auth synced with Codex CLI.');
|
|
179
|
+
return { status: 'present', ...status, codex_login: codexLogin };
|
|
180
|
+
}
|
|
176
181
|
const useCodexLb = (await askPostinstallQuestion('\nAuthenticate and route Codex through codex-lb? [y/N] ')).trim();
|
|
177
182
|
if (!/^(y|yes|예|네|응)$/i.test(useCodexLb)) return { status: 'continued_to_codex' };
|
|
178
183
|
const host = (await askPostinstallQuestion('codex-lb host domain [http://127.0.0.1:2455]: ')).trim() || 'http://127.0.0.1:2455';
|
|
@@ -183,6 +188,28 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
183
188
|
return configured;
|
|
184
189
|
}
|
|
185
190
|
|
|
191
|
+
async function ensureCodexLbLoginFromEnv(status = {}, opts = {}) {
|
|
192
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
193
|
+
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
194
|
+
const apiKey = parseCodexLbEnvKey(await readText(envPath, ''));
|
|
195
|
+
if (!apiKey) return { ok: false, status: 'missing_env_key' };
|
|
196
|
+
return syncCodexApiKeyLogin(apiKey, { ...opts, home });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
200
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
201
|
+
const codexHome = opts.codexHome || path.join(home, '.codex');
|
|
202
|
+
const codexBin = opts.codexBin || (await getCodexInfo().catch(() => ({}))).bin || await which('codex').catch(() => null);
|
|
203
|
+
if (!codexBin) return { ok: false, status: 'codex_missing' };
|
|
204
|
+
await ensureDir(codexHome);
|
|
205
|
+
const env = { HOME: home, CODEX_HOME: codexHome, CODEX_LB_API_KEY: apiKey };
|
|
206
|
+
const current = await runProcess(codexBin, ['login', 'status'], { env, timeoutMs: 10000, maxOutputBytes: 8192 });
|
|
207
|
+
if (current.code === 0 && !/not logged in/i.test(`${current.stdout}\n${current.stderr}`)) return { ok: true, status: 'present' };
|
|
208
|
+
const login = await runProcess(codexBin, ['login', '--with-api-key'], { input: `${apiKey}\n`, env, timeoutMs: 15000, maxOutputBytes: 8192 });
|
|
209
|
+
if (login.code === 0) return { ok: true, status: 'synced' };
|
|
210
|
+
return { ok: false, status: 'login_failed', error: (login.stderr || login.stdout || 'codex login failed').trim() };
|
|
211
|
+
}
|
|
212
|
+
|
|
186
213
|
function upsertCodexLbConfig(text = '', baseUrl) {
|
|
187
214
|
let next = upsertTopLevelTomlString(text, 'model_provider', 'codex-lb');
|
|
188
215
|
const block = [
|
|
@@ -235,6 +262,15 @@ function shellSingleQuote(value) {
|
|
|
235
262
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
236
263
|
}
|
|
237
264
|
|
|
265
|
+
function parseCodexLbEnvKey(text = '') {
|
|
266
|
+
const match = String(text || '').match(/^\s*(?:export\s+)?CODEX_LB_API_KEY\s*=\s*(.+?)\s*$/m);
|
|
267
|
+
if (!match) return '';
|
|
268
|
+
const raw = match[1].trim();
|
|
269
|
+
if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1).replace(/'\\''/g, "'");
|
|
270
|
+
if (raw.startsWith('"') && raw.endsWith('"')) return raw.slice(1, -1).replace(/\\"/g, '"');
|
|
271
|
+
return raw;
|
|
272
|
+
}
|
|
273
|
+
|
|
238
274
|
function escapeRegExp(value) {
|
|
239
275
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
240
276
|
}
|
package/src/cli/main.mjs
CHANGED
|
@@ -2007,9 +2007,11 @@ async function selftest() {
|
|
|
2007
2007
|
const codexLbSetupJson = JSON.parse(codexLbSetup.stdout);
|
|
2008
2008
|
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
2009
2009
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
2010
|
-
|
|
2010
|
+
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
2011
|
+
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'") || !codexLbAuth.includes('"auth_mode": "apikey"')) throw new Error('selftest failed: codex-lb setup did not write provider config, env key, and Codex API-key auth');
|
|
2011
2012
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
2012
2013
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
|
|
2014
|
+
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');
|
|
2013
2015
|
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');
|
|
2014
2016
|
if (shouldAutoAttachTmux(['--mad', '--json'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux json mode should not auto-attach');
|
|
2015
2017
|
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.42';
|
|
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/tmux-ui.mjs
CHANGED
|
@@ -6,14 +6,34 @@ import { getCodexInfo } from './codex-adapter.mjs';
|
|
|
6
6
|
import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
|
|
7
7
|
|
|
8
8
|
export const SKS_TMUX_LOGO = [
|
|
9
|
-
' _____
|
|
10
|
-
' / ___
|
|
11
|
-
' \\__
|
|
12
|
-
' ___/ / /| |
|
|
13
|
-
'/____/_/ |_
|
|
14
|
-
'
|
|
9
|
+
' _____ __ __ _____',
|
|
10
|
+
' / ___/ / //_/ / ___/',
|
|
11
|
+
' \\__ \\ / ,< \\__ \\ ',
|
|
12
|
+
' ___/ / / /| | ___/ / ',
|
|
13
|
+
'/____/ /_/ |_| /____/ ',
|
|
14
|
+
' SNEAKOSCOPE CODEX'
|
|
15
15
|
].join('\n');
|
|
16
16
|
|
|
17
|
+
const SKS_TMUX_LOGO_FRAMES = [
|
|
18
|
+
[
|
|
19
|
+
' __ __',
|
|
20
|
+
' / //_/ .',
|
|
21
|
+
' / ,< .',
|
|
22
|
+
' / /| | .',
|
|
23
|
+
' /_/ |_|',
|
|
24
|
+
' S K S'
|
|
25
|
+
].join('\n'),
|
|
26
|
+
[
|
|
27
|
+
' _____ __ __ _____',
|
|
28
|
+
' / ___/ / //_/ / ___/',
|
|
29
|
+
' \\__ \\ / ,< \\__ \\ ',
|
|
30
|
+
' / / /| | / / ',
|
|
31
|
+
' /_/ /_/ |_| /_/ ',
|
|
32
|
+
' SNEAKOSCOPE'
|
|
33
|
+
].join('\n'),
|
|
34
|
+
SKS_TMUX_LOGO
|
|
35
|
+
];
|
|
36
|
+
|
|
17
37
|
export const DEFAULT_SKS_CODEX_MODEL = 'gpt-5.5';
|
|
18
38
|
export const DEFAULT_SKS_CODEX_REASONING = 'high';
|
|
19
39
|
|
|
@@ -103,8 +123,7 @@ export function tmuxStatusKind(tmux = {}) {
|
|
|
103
123
|
export function codexLaunchCommand(root, codexBin, codexArgs = []) {
|
|
104
124
|
const extraArgs = Array.isArray(codexArgs) ? codexArgs : [];
|
|
105
125
|
return [
|
|
106
|
-
|
|
107
|
-
`printf '%s\\n' ${shellEscape(SKS_TMUX_LOGO)}`,
|
|
126
|
+
sksLogoIntroCommand(),
|
|
108
127
|
`printf '\\nProject: %s\\n' ${shellEscape(root)}`,
|
|
109
128
|
'printf \'Runtime: tmux session for Codex CLI\\n\'',
|
|
110
129
|
'printf \'Prompt: use canonical $ commands, for example $Team or $QA-LOOP\\n\\n\'',
|
|
@@ -114,6 +133,22 @@ export function codexLaunchCommand(root, codexBin, codexArgs = []) {
|
|
|
114
133
|
].join('; ');
|
|
115
134
|
}
|
|
116
135
|
|
|
136
|
+
export function sksLogoIntroCommand() {
|
|
137
|
+
const staticLogo = `clear; printf '\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO)}`;
|
|
138
|
+
const animated = [
|
|
139
|
+
'clear',
|
|
140
|
+
`printf '\\033[38;5;39m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[0])}`,
|
|
141
|
+
'sleep 0.07',
|
|
142
|
+
'clear',
|
|
143
|
+
`printf '\\033[1;38;5;45m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[1])}`,
|
|
144
|
+
'sleep 0.08',
|
|
145
|
+
'clear',
|
|
146
|
+
`printf '\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[2])}`,
|
|
147
|
+
'sleep 0.15'
|
|
148
|
+
].join('; ');
|
|
149
|
+
return `if [ "\${SKS_TMUX_LOGO_ANIMATION:-1}" = "0" ]; then ${staticLogo}; else ${animated}; fi`;
|
|
150
|
+
}
|
|
151
|
+
|
|
117
152
|
function terminalTitleCommand(title = '') {
|
|
118
153
|
return `printf '\\033]0;%s\\007' ${shellEscape(String(title || '').slice(0, 80))}`;
|
|
119
154
|
}
|