sneakoscope 4.7.1 → 4.7.4

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.
Files changed (36) hide show
  1. package/README.md +11 -11
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -1
  7. package/dist/cli/install-helpers.js +215 -57
  8. package/dist/commands/codex-lb.js +143 -7
  9. package/dist/commands/doctor.js +59 -2
  10. package/dist/core/codex-app/codex-app-fast-ui-repair.js +11 -1
  11. package/dist/core/codex-app/codex-app-restart.js +65 -0
  12. package/dist/core/codex-app/sks-menubar.js +445 -0
  13. package/dist/core/codex-app.js +239 -3
  14. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -2
  15. package/dist/core/codex-control/codex-task-runner.js +2 -2
  16. package/dist/core/codex-lb/codex-lb-setup.js +1 -1
  17. package/dist/core/commands/basic-cli.js +14 -7
  18. package/dist/core/doctor/doctor-dirty-planner.js +2 -0
  19. package/dist/core/doctor/doctor-readiness-matrix.js +4 -0
  20. package/dist/core/fsx.js +1 -1
  21. package/dist/core/imagegen/imagegen-capability.js +2 -2
  22. package/dist/core/init.js +1 -1
  23. package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
  24. package/dist/core/provider/provider-context.js +1 -1
  25. package/dist/core/routes.js +2 -2
  26. package/dist/core/update-check.js +67 -18
  27. package/dist/core/version.js +1 -1
  28. package/dist/scripts/codex-app-provider-model-ui-check.js +114 -0
  29. package/dist/scripts/codex-lb-fast-mode-truth-check.js +90 -0
  30. package/dist/scripts/codex-lb-setup-truthfulness-check.js +6 -1
  31. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +5 -1
  32. package/dist/scripts/gpt-image-2-real-file-smoke.js +2 -2
  33. package/dist/scripts/provider-context-config-toml-check.js +2 -2
  34. package/dist/scripts/sks-menubar-install-check.js +59 -0
  35. package/dist/scripts/update-default-command-check.js +28 -0
  36. package/package.json +5 -1
@@ -0,0 +1,445 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { ensureDir, exists, runProcess, which, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
5
+ const LABEL = 'com.sneakoscope.sks-menubar';
6
+ const MENU_ITEMS = [
7
+ 'Use codex-lb',
8
+ 'Use ChatGPT OAuth',
9
+ 'Set OpenRouter Key and GLM Profiles',
10
+ 'Fast Check',
11
+ 'SKS Version Check',
12
+ 'Update SKS Now',
13
+ 'Open Codex Settings',
14
+ 'Restart Codex',
15
+ 'Quit SKS Menu'
16
+ ];
17
+ export async function installSksMenuBar(opts = {}) {
18
+ const apply = opts.apply === true;
19
+ const env = opts.env || process.env;
20
+ const home = path.resolve(opts.home || env.HOME || os.homedir());
21
+ const root = path.resolve(opts.root || process.cwd());
22
+ const installDir = path.join(home, '.codex', 'sks-menubar');
23
+ const appPath = path.join(installDir, 'SKSMenuBar.app');
24
+ const contentsPath = path.join(appPath, 'Contents');
25
+ const macosPath = path.join(contentsPath, 'MacOS');
26
+ const executablePath = path.join(macosPath, 'SKSMenuBar');
27
+ const sourcePath = path.join(installDir, 'SKSMenuBar.swift');
28
+ const infoPlistPath = path.join(contentsPath, 'Info.plist');
29
+ const actionScriptPath = path.join(installDir, 'sks-menubar-action.sh');
30
+ const launchAgentPath = path.join(home, 'Library', 'LaunchAgents', `${LABEL}.plist`);
31
+ const reportPath = path.join(root, '.sneakoscope', 'reports', 'sks-menubar.json');
32
+ const actions = [];
33
+ const warnings = [];
34
+ if (process.platform !== 'darwin') {
35
+ const result = {
36
+ schema: 'sks.codex-app-sks-menubar.v1',
37
+ ok: true,
38
+ apply,
39
+ status: 'unsupported_platform',
40
+ platform: process.platform,
41
+ app_path: null,
42
+ executable_path: null,
43
+ launch_agent_path: null,
44
+ action_script_path: null,
45
+ report_path: apply ? reportPath : null,
46
+ menu_items: MENU_ITEMS,
47
+ actions: [],
48
+ launch: { requested: false, method: 'none', ok: true },
49
+ blockers: [],
50
+ warnings: ['sks_menubar_requires_macos']
51
+ };
52
+ if (apply)
53
+ await writeJsonAtomic(reportPath, result).catch(() => undefined);
54
+ return result;
55
+ }
56
+ if (!apply) {
57
+ const installed = await exists(executablePath);
58
+ const launchAgent = await exists(launchAgentPath);
59
+ return {
60
+ schema: 'sks.codex-app-sks-menubar.v1',
61
+ ok: true,
62
+ apply,
63
+ status: 'planned',
64
+ platform: process.platform,
65
+ app_path: appPath,
66
+ executable_path: executablePath,
67
+ launch_agent_path: launchAgentPath,
68
+ action_script_path: actionScriptPath,
69
+ report_path: reportPath,
70
+ menu_items: MENU_ITEMS,
71
+ actions: installed ? ['menubar_app_present'] : ['menubar_app_install_available'],
72
+ launch: {
73
+ requested: false,
74
+ method: 'skipped',
75
+ ok: true
76
+ },
77
+ blockers: [],
78
+ warnings: launchAgent ? [] : ['launch_agent_not_installed_yet']
79
+ };
80
+ }
81
+ const swiftc = env.SKS_MENUBAR_SWIFTC || await which('swiftc').catch(() => null) || await fallbackTool('/usr/bin/swiftc');
82
+ const launchctl = env.SKS_MENUBAR_LAUNCHCTL || await which('launchctl').catch(() => null) || await fallbackTool('/bin/launchctl');
83
+ const open = env.SKS_MENUBAR_OPEN || await which('open').catch(() => null) || await fallbackTool('/usr/bin/open');
84
+ if (!swiftc)
85
+ return await blockedResult('swiftc_missing');
86
+ await ensureDir(installDir);
87
+ await ensureDir(macosPath);
88
+ await ensureDir(path.dirname(launchAgentPath));
89
+ const sksEntry = resolveSksEntry(opts.sksEntry);
90
+ await writeTextAtomic(actionScriptPath, actionScriptSource({ nodeBin: process.execPath, sksEntry }));
91
+ await fs.chmod(actionScriptPath, 0o755);
92
+ actions.push(`wrote ${actionScriptPath}`);
93
+ await writeTextAtomic(sourcePath, swiftMenuSource(actionScriptPath));
94
+ actions.push(`wrote ${sourcePath}`);
95
+ await writeTextAtomic(infoPlistPath, infoPlistSource());
96
+ actions.push(`wrote ${infoPlistPath}`);
97
+ const compile = await runProcess(swiftc, ['-framework', 'Cocoa', sourcePath, '-o', executablePath], {
98
+ timeoutMs: 60_000,
99
+ maxOutputBytes: 64 * 1024
100
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
101
+ if (compile.code !== 0) {
102
+ return await blockedResult('swift_compile_failed', String(compile.stderr || compile.stdout || '').trim());
103
+ }
104
+ await fs.chmod(executablePath, 0o755).catch(() => undefined);
105
+ actions.push(`compiled ${executablePath}`);
106
+ await writeTextAtomic(launchAgentPath, launchAgentSource(executablePath, installDir));
107
+ actions.push(`wrote ${launchAgentPath}`);
108
+ const launchRequested = opts.launch !== false && env.SKS_SKIP_SKS_MENUBAR_LAUNCH !== '1';
109
+ const launch = launchRequested && launchctl
110
+ ? await launchWithLaunchctl({ launchctl, open, appPath, executablePath, launchAgentPath })
111
+ : {
112
+ requested: launchRequested,
113
+ method: 'skipped',
114
+ ok: !launchRequested,
115
+ error: launchRequested ? 'launchctl_missing' : null
116
+ };
117
+ if (launchRequested && !launchctl)
118
+ warnings.push('launchctl_missing');
119
+ if (launch.method === 'open-fallback')
120
+ warnings.push('launchctl_bootstrap_failed_open_fallback_used');
121
+ const ok = launch.ok === true;
122
+ const result = {
123
+ schema: 'sks.codex-app-sks-menubar.v1',
124
+ ok,
125
+ apply,
126
+ status: ok
127
+ ? launch.requested === false || launch.method === 'skipped'
128
+ ? 'installed_launch_skipped'
129
+ : launch.method === 'open-fallback'
130
+ ? 'installed_open_fallback'
131
+ : 'installed'
132
+ : 'blocked',
133
+ platform: process.platform,
134
+ app_path: appPath,
135
+ executable_path: executablePath,
136
+ launch_agent_path: launchAgentPath,
137
+ action_script_path: actionScriptPath,
138
+ report_path: reportPath,
139
+ menu_items: MENU_ITEMS,
140
+ actions,
141
+ launch,
142
+ blockers: ok ? [] : [launch.error || 'sks_menubar_launch_failed'],
143
+ warnings
144
+ };
145
+ await writeJsonAtomic(reportPath, result).catch(() => undefined);
146
+ return result;
147
+ async function blockedResult(reason, detail) {
148
+ const result = {
149
+ schema: 'sks.codex-app-sks-menubar.v1',
150
+ ok: false,
151
+ apply,
152
+ status: 'blocked',
153
+ platform: process.platform,
154
+ app_path: appPath,
155
+ executable_path: executablePath,
156
+ launch_agent_path: launchAgentPath,
157
+ action_script_path: actionScriptPath,
158
+ report_path: reportPath,
159
+ menu_items: MENU_ITEMS,
160
+ actions,
161
+ launch: { requested: false, method: 'none', ok: false, error: detail || reason },
162
+ blockers: [reason],
163
+ warnings: detail ? [detail] : []
164
+ };
165
+ await writeJsonAtomic(reportPath, result).catch(() => undefined);
166
+ return result;
167
+ }
168
+ }
169
+ async function fallbackTool(candidate) {
170
+ return await exists(candidate).then((ok) => ok ? candidate : null).catch(() => null);
171
+ }
172
+ function resolveSksEntry(explicit) {
173
+ if (explicit)
174
+ return path.resolve(explicit);
175
+ const argvEntry = process.argv[1] ? path.resolve(process.argv[1]) : '';
176
+ return argvEntry || path.join(process.cwd(), 'dist', 'bin', 'sks.js');
177
+ }
178
+ function actionScriptSource(input) {
179
+ return `#!/bin/zsh
180
+ set -e
181
+ export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
182
+ NODE_BIN=${shellQuote(input.nodeBin)}
183
+ SKS_ENTRY=${shellQuote(input.sksEntry)}
184
+ if [ -x "$NODE_BIN" ] && [ -f "$SKS_ENTRY" ]; then
185
+ exec "$NODE_BIN" "$SKS_ENTRY" "$@"
186
+ fi
187
+ if command -v sks >/dev/null 2>&1; then
188
+ exec sks "$@"
189
+ fi
190
+ echo "SKS command not found. Run npm link or install Sneakoscope Codex, then run sks doctor --fix again." >&2
191
+ exit 127
192
+ `;
193
+ }
194
+ function swiftMenuSource(actionScriptPath) {
195
+ return `import Cocoa
196
+ import Foundation
197
+
198
+ let actionScript = ${swiftString(actionScriptPath)}
199
+
200
+ func shellQuote(_ value: String) -> String {
201
+ return "'" + value.replacingOccurrences(of: "'", with: "'\\\\''") + "'"
202
+ }
203
+
204
+ func runDetached(_ executable: String, _ args: [String] = []) {
205
+ let process = Process()
206
+ process.executableURL = URL(fileURLWithPath: executable)
207
+ process.arguments = args
208
+ try? process.run()
209
+ }
210
+
211
+ func runInTerminal(_ command: String) {
212
+ let escaped = command
213
+ .replacingOccurrences(of: "\\\\", with: "\\\\\\\\")
214
+ .replacingOccurrences(of: "\\\"", with: "\\\\\\\"")
215
+ let script = "tell application \\"Terminal\\" to activate\\n" +
216
+ "tell application \\"Terminal\\" to do script \\"\(escaped)\\""
217
+ runDetached("/usr/bin/osascript", ["-e", script])
218
+ }
219
+
220
+ final class AppDelegate: NSObject, NSApplicationDelegate {
221
+ var statusItem: NSStatusItem!
222
+
223
+ func applicationDidFinishLaunching(_ notification: Notification) {
224
+ NSApp.setActivationPolicy(.accessory)
225
+ statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
226
+ if let button = statusItem.button {
227
+ button.title = "SKS"
228
+ button.toolTip = "Sneakoscope Codex settings"
229
+ }
230
+
231
+ let menu = NSMenu()
232
+ add(menu, "Use codex-lb", #selector(useCodexLb))
233
+ add(menu, "Use ChatGPT OAuth", #selector(useChatGptOAuth))
234
+ menu.addItem(NSMenuItem.separator())
235
+ add(menu, "Set OpenRouter Key and GLM Profiles", #selector(setOpenRouterKey))
236
+ add(menu, "Fast Check", #selector(fastCheck))
237
+ add(menu, "SKS Version Check", #selector(sksVersionCheck))
238
+ add(menu, "Update SKS Now", #selector(updateSksNow))
239
+ menu.addItem(NSMenuItem.separator())
240
+ add(menu, "Open Codex Settings", #selector(openCodexSettings))
241
+ add(menu, "Restart Codex", #selector(restartCodex))
242
+ menu.addItem(NSMenuItem.separator())
243
+ add(menu, "Quit SKS Menu", #selector(quit))
244
+ statusItem.menu = menu
245
+ }
246
+
247
+ func add(_ menu: NSMenu, _ title: String, _ selector: Selector) {
248
+ let item = NSMenuItem(title: title, action: selector, keyEquivalent: "")
249
+ item.target = self
250
+ menu.addItem(item)
251
+ }
252
+
253
+ func runSks(_ args: [String], tail: String = "echo; echo 'SKS command finished. Close this window when ready.'") {
254
+ let quoted = args.map(shellQuote).joined(separator: " ")
255
+ runInTerminal("\\(shellQuote(actionScript)) \\(quoted); \\(tail)")
256
+ }
257
+
258
+ @objc func useCodexLb() {
259
+ runSks(["codex-lb", "use-codex-lb"])
260
+ }
261
+
262
+ @objc func useChatGptOAuth() {
263
+ runSks(["codex-lb", "use-oauth"])
264
+ }
265
+
266
+ @objc func setOpenRouterKey() {
267
+ let command = "printf 'Paste OpenRouter key, then press Return: '; read -r key; printf '%s\\\\n' \\"$key\\" | \\(shellQuote(actionScript)) codex-app set-openrouter-key --api-key-stdin; \\(shellQuote(actionScript)) codex-app glm-profile install; echo; echo 'OpenRouter/GLM update finished. Restart Codex if the model picker was already open.'"
268
+ runInTerminal(command)
269
+ }
270
+
271
+ @objc func fastCheck() {
272
+ runSks(["codex-lb", "fast-check"])
273
+ }
274
+
275
+ @objc func sksVersionCheck() {
276
+ let sks = shellQuote(actionScript)
277
+ runInTerminal("echo 'SKS version'; \\(sks) --version; echo; echo 'Checking npm latest'; \\(sks) update check; echo; echo 'SKS version check finished. Close this window when ready.'")
278
+ }
279
+
280
+ @objc func updateSksNow() {
281
+ runSks(["update"], tail: "echo; echo 'SKS update finished. Close this window when ready.'")
282
+ }
283
+
284
+ @objc func openCodexSettings() {
285
+ runDetached("/usr/bin/open", ["codex://settings"])
286
+ }
287
+
288
+ @objc func restartCodex() {
289
+ runInTerminal("/usr/bin/osascript -e 'tell application \\"Codex\\" to quit'; sleep 1; /usr/bin/open -a Codex; echo 'Codex restart requested.'")
290
+ }
291
+
292
+ @objc func quit() {
293
+ NSApplication.shared.terminate(nil)
294
+ }
295
+ }
296
+
297
+ let app = NSApplication.shared
298
+ let delegate = AppDelegate()
299
+ app.delegate = delegate
300
+ app.run()
301
+ `;
302
+ }
303
+ function infoPlistSource() {
304
+ return `<?xml version="1.0" encoding="UTF-8"?>
305
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
306
+ <plist version="1.0">
307
+ <dict>
308
+ <key>CFBundleExecutable</key>
309
+ <string>SKSMenuBar</string>
310
+ <key>CFBundleIdentifier</key>
311
+ <string>${LABEL}</string>
312
+ <key>CFBundleName</key>
313
+ <string>SKS Menu Bar</string>
314
+ <key>CFBundlePackageType</key>
315
+ <string>APPL</string>
316
+ <key>CFBundleVersion</key>
317
+ <string>1</string>
318
+ <key>LSUIElement</key>
319
+ <true/>
320
+ </dict>
321
+ </plist>
322
+ `;
323
+ }
324
+ function launchAgentSource(executablePath, installDir) {
325
+ return `<?xml version="1.0" encoding="UTF-8"?>
326
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
327
+ <plist version="1.0">
328
+ <dict>
329
+ <key>Label</key>
330
+ <string>${LABEL}</string>
331
+ <key>ProgramArguments</key>
332
+ <array>
333
+ <string>${escapeXml(executablePath)}</string>
334
+ </array>
335
+ <key>RunAtLoad</key>
336
+ <true/>
337
+ <key>KeepAlive</key>
338
+ <true/>
339
+ <key>StandardOutPath</key>
340
+ <string>${escapeXml(path.join(installDir, 'menubar.out.log'))}</string>
341
+ <key>StandardErrorPath</key>
342
+ <string>${escapeXml(path.join(installDir, 'menubar.err.log'))}</string>
343
+ </dict>
344
+ </plist>
345
+ `;
346
+ }
347
+ async function launchWithLaunchctl(input) {
348
+ const uid = typeof process.getuid === 'function' ? process.getuid() : null;
349
+ const domain = uid === null ? 'gui' : `gui/${uid}`;
350
+ await runProcess(input.launchctl, ['bootout', `${domain}/${LABEL}`], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
351
+ await runProcess(input.launchctl, ['bootout', domain, input.launchAgentPath], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
352
+ await terminateExistingMenuBarProcess(input.executablePath);
353
+ const bootstrap = await runProcess(input.launchctl, ['bootstrap', domain, input.launchAgentPath], {
354
+ timeoutMs: 10_000,
355
+ maxOutputBytes: 32 * 1024
356
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
357
+ if (bootstrap.code === 0) {
358
+ const kickstart = await runProcess(input.launchctl, ['kickstart', '-k', `${domain}/${LABEL}`], {
359
+ timeoutMs: 5_000,
360
+ maxOutputBytes: 32 * 1024
361
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
362
+ if (kickstart.code !== 0) {
363
+ const printed = await waitForLaunchctlRunning(input.launchctl, `${domain}/${LABEL}`);
364
+ if (printed.running) {
365
+ return {
366
+ requested: true,
367
+ method: 'launchctl',
368
+ ok: true,
369
+ bootstrap_code: bootstrap.code,
370
+ kickstart_code: kickstart.code,
371
+ print_code: printed.code,
372
+ error: null
373
+ };
374
+ }
375
+ }
376
+ return {
377
+ requested: true,
378
+ method: 'launchctl',
379
+ ok: kickstart.code === 0,
380
+ bootstrap_code: bootstrap.code,
381
+ kickstart_code: kickstart.code,
382
+ error: kickstart.code === 0 ? null : String(kickstart.stderr || kickstart.stdout || '').trim() || 'launchctl_kickstart_failed'
383
+ };
384
+ }
385
+ if (input.open) {
386
+ const opened = await runProcess(input.open, [input.appPath], {
387
+ timeoutMs: 10_000,
388
+ maxOutputBytes: 32 * 1024
389
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
390
+ return {
391
+ requested: true,
392
+ method: 'open-fallback',
393
+ ok: opened.code === 0,
394
+ bootstrap_code: bootstrap.code,
395
+ open_code: opened.code,
396
+ error: opened.code === 0
397
+ ? null
398
+ : String(opened.stderr || opened.stdout || bootstrap.stderr || bootstrap.stdout || '').trim() || 'sks_menubar_launch_failed'
399
+ };
400
+ }
401
+ return {
402
+ requested: true,
403
+ method: 'launchctl',
404
+ ok: false,
405
+ bootstrap_code: bootstrap.code,
406
+ error: String(bootstrap.stderr || bootstrap.stdout || '').trim() || 'launchctl_bootstrap_failed'
407
+ };
408
+ }
409
+ async function waitForLaunchctlRunning(launchctl, service) {
410
+ let lastCode = null;
411
+ for (let attempt = 0; attempt < 30; attempt += 1) {
412
+ const printed = await runProcess(launchctl, ['print', service], {
413
+ timeoutMs: 2_000,
414
+ maxOutputBytes: 64 * 1024
415
+ }).catch(() => ({ code: 1, stdout: '', stderr: '' }));
416
+ lastCode = printed.code;
417
+ if (printed.code === 0 && /\bstate = running\b|\bpid = \d+\b/.test(`${printed.stdout || ''}\n${printed.stderr || ''}`)) {
418
+ return { code: printed.code, running: true };
419
+ }
420
+ await new Promise((resolve) => setTimeout(resolve, 500));
421
+ }
422
+ return { code: lastCode, running: false };
423
+ }
424
+ async function terminateExistingMenuBarProcess(executablePath) {
425
+ const pkill = await which('pkill').catch(() => null) || await fallbackTool('/usr/bin/pkill');
426
+ if (!pkill)
427
+ return;
428
+ await runProcess(pkill, ['-f', executablePath], { timeoutMs: 5_000, maxOutputBytes: 8 * 1024 }).catch(() => undefined);
429
+ await new Promise((resolve) => setTimeout(resolve, 250));
430
+ }
431
+ function swiftString(value) {
432
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
433
+ }
434
+ function shellQuote(value) {
435
+ return `'${value.replace(/'/g, `'\\''`)}'`;
436
+ }
437
+ function escapeXml(value) {
438
+ return value
439
+ .replace(/&/g, '&amp;')
440
+ .replace(/</g, '&lt;')
441
+ .replace(/>/g, '&gt;')
442
+ .replace(/"/g, '&quot;')
443
+ .replace(/'/g, '&apos;');
444
+ }
445
+ //# sourceMappingURL=sks-menubar.js.map