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 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`, or disable the default with `SKS_CODEX_FAST_HIGH=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.
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.41",
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
- return { ok: true, status: 'configured', config_path: configPath, env_path: envPath, base_url: baseUrl, env_key: 'CODEX_LB_API_KEY' };
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) return { status: 'present', ...status };
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
- 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');
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.41';
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
 
@@ -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
- 'Sneakoscope Codex tmux'
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
- 'clear',
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
  }