sneakoscope 4.7.3 โ 4.8.0
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 +7 -7
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/commands/doctor.js +50 -2
- package/dist/core/codex-app/sks-menubar.js +458 -0
- package/dist/core/codex-app.js +5 -1
- package/dist/core/commands/basic-cli.js +14 -7
- package/dist/core/doctor/doctor-dirty-planner.js +2 -0
- package/dist/core/doctor/doctor-readiness-matrix.js +4 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
- package/dist/core/routes.js +1 -1
- package/dist/core/update-check.js +67 -18
- package/dist/core/version.js +1 -1
- package/dist/scripts/sks-menubar-install-check.js +67 -0
- package/dist/scripts/update-default-command-check.js +28 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ sks seo-geo-optimizer apply latest --mode seo --apply --json
|
|
|
49
49
|
sks seo-geo-optimizer audit --mode geo --target package --offline --json
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
> ๐ **Current release: `v4.
|
|
52
|
+
> ๐ **Current release: `v4.8.0`** โ full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
|
|
53
53
|
|
|
54
54
|
## ๐ฅ Parallelism, UX, And Integrations
|
|
55
55
|
|
|
@@ -74,7 +74,7 @@ sks seo-geo-optimizer audit --mode geo --target package --offline --json
|
|
|
74
74
|
sks insane-search x "site:x.com product launch"
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
- **CLI-only SKS update notices.** Codex App hooks no longer stop normal work to ask for an SKS update. CLI launch surfaces such as `sks --mad` print a non-blocking latest-version notice, `sks update-check` / `sks update check` show the explicit status, and `sks doctor --fix` runs the guarded
|
|
77
|
+
- **CLI-only SKS update notices.** Codex App hooks no longer stop normal work to ask for an SKS update. CLI launch surfaces such as `sks --mad` print a non-blocking latest-version notice, `sks update-check` / `sks update check` show the explicit status, bare `sks update` checks npm latest and updates the global package, and `sks doctor --fix` runs the guarded repair path.
|
|
78
78
|
|
|
79
79
|
## ๐งน Retention And Cleanup
|
|
80
80
|
|
|
@@ -318,7 +318,7 @@ sks deps check --yes
|
|
|
318
318
|
sks codex-app check
|
|
319
319
|
sks doctor --fix
|
|
320
320
|
sks fix-path
|
|
321
|
-
sks update
|
|
321
|
+
sks update
|
|
322
322
|
```
|
|
323
323
|
|
|
324
324
|
`sks doctor --fix` repairs stale SKS-managed Codex TOML, agent role `config_file` paths, and unsupported managed role fields. It now dedupes repeated TOML tables before repair, keeps the first existing external MCP block such as `context7` or `supabase` unchanged, and only removes/repairs SKS-owned stale MCP blocks such as broken `node_repl`.
|
|
@@ -363,7 +363,7 @@ sks codex-app set-openrouter-key --api-key-stdin
|
|
|
363
363
|
sks codex-app glm-profile install
|
|
364
364
|
```
|
|
365
365
|
|
|
366
|
-
This writes the OpenRouter key to the SKS user secret store, writes redacted metadata only, and installs Codex Desktop-compatible `openrouter` provider plus selectable `sks-glm-52-*` reasoning profiles. The npm package does not patch the Codex Desktop macOS menu binary; a top-level
|
|
366
|
+
This writes the OpenRouter key to the SKS user secret store, writes redacted metadata only, and installs Codex Desktop-compatible `openrouter` provider plus selectable `sks-glm-52-*` reasoning profiles. The npm package does not patch the Codex Desktop macOS menu binary; a top-level menu between Window and Help requires upstream Codex Desktop UI support. On macOS, `sks doctor --fix` installs or refreshes a right-side SKS menu bar companion that appears as a compact circled `S` status icon with codex-lb, ChatGPT OAuth, OpenRouter/GLM, Fast Check, SKS Version Check, Update SKS Now, settings, and Codex restart actions.
|
|
367
367
|
|
|
368
368
|
### Switching auth mode: codex-lb โ ChatGPT OAuth
|
|
369
369
|
|
|
@@ -404,7 +404,7 @@ sks --mad --allow-package-install --allow-service-control --allow-network --yes
|
|
|
404
404
|
|
|
405
405
|
This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` xhigh maintenance profile, opens the MAD-SKS permission gate for that Zellij run, starts a same-mission bounded native agent swarm, and launches a Codex CLI layout whose right-side lanes read that MAD ledger. Bare `sks --mad` grants target-project file and shell scope only; add explicit `--allow-*` flags for packages, services, network, Computer Use, browser use, generated assets, file permissions, DB writes, or other high-risk scopes. MAD-SKS is not a DB-only unlock and does not create a MadDB capability. Catastrophic database wipe/all-row/project-management safeguards remain active outside the first-class MadDB route, and the pipeline contract still forbids unrequested fallback implementation code.
|
|
406
406
|
|
|
407
|
-
Before launching, SKS checks npm for a newer `sneakoscope` and prints a non-blocking update notice when one is available; use `sks update
|
|
407
|
+
Before launching, SKS checks npm for a newer `sneakoscope` and prints a non-blocking update notice when one is available; use `sks update` when you want SKS to update itself from npm. Use `--yes` to approve missing dependency installs automatically. Tune MAD swarm startup with `--mad-agents <n>`, `--mad-swarm-work-items <n>`, and `--mad-swarm-backend <backend>`; `--no-mad-swarm` keeps only the cockpit UI if you need a temporary fallback.
|
|
408
408
|
|
|
409
409
|
### Team Missions
|
|
410
410
|
|
|
@@ -679,12 +679,12 @@ If PATH or npm has duplicate global installs, `sks doctor --fix` keeps one globa
|
|
|
679
679
|
|
|
680
680
|
```sh
|
|
681
681
|
sks update-check --json
|
|
682
|
-
sks update
|
|
682
|
+
sks update
|
|
683
683
|
npm ls -g sneakoscope --depth=0
|
|
684
684
|
sks doctor --fix
|
|
685
685
|
```
|
|
686
686
|
|
|
687
|
-
CLI update checks compare npm latest against the effective installed version from source metadata, PATH `sks --version`, and global npm package metadata. Codex App hooks do not force update choices during ordinary work. `sks update
|
|
687
|
+
CLI update checks compare npm latest against the effective installed version from source metadata, PATH `sks --version`, and global npm package metadata. Codex App hooks do not force update choices during ordinary work. Bare `sks update` checks npm latest and installs through npm global mode instead of mutating the current project's dependencies; `sks update check` is the status-only form, and `sks update now` remains an explicit alias. Successful/current update runs also install or refresh the macOS right-side `SKS` menu bar companion so it is already visible when Codex App is opened. If a global update succeeded but an old shim remains earlier on PATH, `sks doctor --fix` can remove duplicate global installs and refresh the managed setup.
|
|
688
688
|
|
|
689
689
|
### Zellij is missing
|
|
690
690
|
|
|
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
|
|
|
4
4
|
fn main() {
|
|
5
5
|
let mut args = std::env::args().skip(1);
|
|
6
6
|
match args.next().as_deref() {
|
|
7
|
-
Some("--version") => println!("sks-rs 4.
|
|
7
|
+
Some("--version") => println!("sks-rs 4.8.0"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
package/dist/bin/sks.js
CHANGED
|
@@ -85,7 +85,7 @@ export const COMMANDS = {
|
|
|
85
85
|
run: entry('beta', 'Classify and execute a task through the SKS trust kernel', 'dist/core/commands/run-command.js', argsCommand(() => import('../core/commands/run-command.js'), 'runCommand', 'dist/core/commands/run-command.js')),
|
|
86
86
|
status: entry('stable', 'Show concise active mission and trust status', 'dist/core/commands/status-command.js', argsCommand(() => import('../core/commands/status-command.js'), 'statusCommand', 'dist/core/commands/status-command.js')),
|
|
87
87
|
root: entry('stable', 'Show active SKS root', 'dist/commands/root.js', directCommand(() => import('../commands/root.js'), 'dist/commands/root.js')),
|
|
88
|
-
update: entry('stable', 'Update the global SKS npm package', 'dist/core/commands/basic-cli.js', subcommand(() => import(basicModule), 'updateCommand', 'dist/core/commands/basic-cli.js', '
|
|
88
|
+
update: entry('stable', 'Update the global SKS npm package', 'dist/core/commands/basic-cli.js', subcommand(() => import(basicModule), 'updateCommand', 'dist/core/commands/basic-cli.js', 'now')),
|
|
89
89
|
'update-check': entry('stable', 'Check npm package freshness', 'dist/core/commands/basic-cli.js', basicArgs('updateCheckCommand')),
|
|
90
90
|
wizard: entry('stable', 'Open setup wizard help', 'dist/core/commands/basic-cli.js', basicNoArgs('quickstartCommand')),
|
|
91
91
|
usage: entry('stable', 'Show focused usage topic', 'dist/core/commands/basic-cli.js', basicArgs('usageCommand')),
|
package/dist/commands/doctor.js
CHANGED
|
@@ -39,6 +39,7 @@ import { planDoctorDirtyRepair } from '../core/doctor/doctor-dirty-planner.js';
|
|
|
39
39
|
import { doctorRepairPostcheck } from '../core/doctor/doctor-repair-postcheck.js';
|
|
40
40
|
import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
|
|
41
41
|
import { writeProjectUpdateMigrationReceipt } from '../core/update/update-migration-state.js';
|
|
42
|
+
import { installSksMenuBar } from '../core/codex-app/sks-menubar.js';
|
|
42
43
|
export async function run(_command, args = []) {
|
|
43
44
|
const root = await projectRoot();
|
|
44
45
|
const doctorFix = flag(args, '--fix');
|
|
@@ -274,6 +275,27 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
274
275
|
blockers: [err?.message || String(err)]
|
|
275
276
|
}))
|
|
276
277
|
: codexAppUiPlan;
|
|
278
|
+
const sksMenuBar = await installSksMenuBar({
|
|
279
|
+
root,
|
|
280
|
+
apply: doctorFix,
|
|
281
|
+
launch: doctorFix
|
|
282
|
+
}).catch((err) => ({
|
|
283
|
+
schema: 'sks.codex-app-sks-menubar.v1',
|
|
284
|
+
ok: false,
|
|
285
|
+
apply: doctorFix,
|
|
286
|
+
status: 'blocked',
|
|
287
|
+
platform: process.platform,
|
|
288
|
+
app_path: null,
|
|
289
|
+
executable_path: null,
|
|
290
|
+
launch_agent_path: null,
|
|
291
|
+
action_script_path: null,
|
|
292
|
+
report_path: `${root}/.sneakoscope/reports/sks-menubar.json`,
|
|
293
|
+
menu_items: [],
|
|
294
|
+
actions: [],
|
|
295
|
+
launch: { requested: doctorFix, method: 'none', ok: false, error: err?.message || String(err) },
|
|
296
|
+
blockers: [err?.message || String(err)],
|
|
297
|
+
warnings: []
|
|
298
|
+
}));
|
|
277
299
|
const zellijRepair = shouldRunZellijRepair
|
|
278
300
|
? await runDoctorZellijRepair({ root, args, doctorFix }).catch((err) => ({
|
|
279
301
|
schema: 'sks.zellij-self-heal.v1',
|
|
@@ -434,6 +456,20 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
434
456
|
rollback_evidence: 'optional_supabase_no_ready_mutation_required'
|
|
435
457
|
})
|
|
436
458
|
},
|
|
459
|
+
{
|
|
460
|
+
id: 'sks_menubar',
|
|
461
|
+
required_for_ready: false,
|
|
462
|
+
run: async () => ({
|
|
463
|
+
id: 'sks_menubar',
|
|
464
|
+
ok: sksMenuBar?.ok !== false,
|
|
465
|
+
repaired: doctorFix && Array.isArray(sksMenuBar?.actions) && sksMenuBar.actions.length > 0,
|
|
466
|
+
required_for_ready: false,
|
|
467
|
+
blockers: sksMenuBar?.blockers || [],
|
|
468
|
+
warnings: sksMenuBar?.warnings || [],
|
|
469
|
+
artifact_path: sksMenuBar?.report_path || null,
|
|
470
|
+
rollback_evidence: sksMenuBar?.launch_agent_path || sksMenuBar?.report_path || 'sks_menubar_optional_no_core_mutation'
|
|
471
|
+
})
|
|
472
|
+
},
|
|
437
473
|
{
|
|
438
474
|
id: 'command_alias_cleanup',
|
|
439
475
|
run: async () => ({
|
|
@@ -621,6 +657,7 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
621
657
|
agent_role_config: agentRoleConfigRepair,
|
|
622
658
|
repair: configRepair,
|
|
623
659
|
codex_app_ui: codexAppUi,
|
|
660
|
+
sks_menubar: sksMenuBar,
|
|
624
661
|
codex_0138_doctor: codex0138Doctor,
|
|
625
662
|
codex_plugin_inventory: pluginInventory?.report || null,
|
|
626
663
|
codex_plugin_app_template_policy: pluginPolicy,
|
|
@@ -658,6 +695,7 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
658
695
|
rust,
|
|
659
696
|
codex_app: codexApp,
|
|
660
697
|
codex_app_ui: codexAppUi,
|
|
698
|
+
sks_menubar: sksMenuBar,
|
|
661
699
|
provider_context: providerContext,
|
|
662
700
|
codex_lb: codexLb,
|
|
663
701
|
codex_doctor: authoritativeCodexDoctor,
|
|
@@ -697,7 +735,7 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
697
735
|
ready,
|
|
698
736
|
sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
|
|
699
737
|
package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
|
|
700
|
-
repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, context7: context7Repair, codex_startup: codexStartupRepair, startup_config: startupConfigRepair, context7_mcp: context7McpRepair, supabase_mcp: supabaseMcpRepair, doctor_transaction: doctorFixTransaction, doctor_dirty_plan: doctorDirtyPlan, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
|
|
738
|
+
repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, context7: context7Repair, codex_startup: codexStartupRepair, startup_config: startupConfigRepair, context7_mcp: context7McpRepair, supabase_mcp: supabaseMcpRepair, sks_menubar: sksMenuBar, doctor_transaction: doctorFixTransaction, doctor_dirty_plan: doctorDirtyPlan, doctor_postcheck: doctorFixPostcheck, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
|
|
701
739
|
};
|
|
702
740
|
if (reportFile)
|
|
703
741
|
await writeJsonReportFile(reportFile, result);
|
|
@@ -821,6 +859,16 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
821
859
|
}
|
|
822
860
|
if (codexAppUi.next_action)
|
|
823
861
|
console.log(` next action: ${codexAppUi.next_action}`);
|
|
862
|
+
console.log('SKS Menu Bar:');
|
|
863
|
+
console.log(` status: ${sksMenuBar.status || (sksMenuBar.ok ? 'ok' : 'blocked')}`);
|
|
864
|
+
if (sksMenuBar.app_path)
|
|
865
|
+
console.log(` app: ${sksMenuBar.app_path}`);
|
|
866
|
+
if (sksMenuBar.launch_agent_path)
|
|
867
|
+
console.log(` launch agent: ${sksMenuBar.launch_agent_path}`);
|
|
868
|
+
if (Array.isArray(sksMenuBar.blockers) && sksMenuBar.blockers.length)
|
|
869
|
+
console.log(` blockers: ${sksMenuBar.blockers.join(', ')}`);
|
|
870
|
+
if (Array.isArray(sksMenuBar.warnings) && sksMenuBar.warnings.length)
|
|
871
|
+
console.log(` warnings: ${sksMenuBar.warnings.join(', ')}`);
|
|
824
872
|
console.log(`Provider: ${providerContext.provider || 'unknown'} ${providerContext.service_tier || ''} (${providerContext.source || 'unknown'}, ${providerContext.confidence || 'low'})`);
|
|
825
873
|
const imagegenReady = imagegen.auth_readiness;
|
|
826
874
|
if (imagegenReady) {
|
|
@@ -989,7 +1037,7 @@ function doctorPhaseIdsForProfile(profile) {
|
|
|
989
1037
|
];
|
|
990
1038
|
if (profile === 'migration')
|
|
991
1039
|
return required;
|
|
992
|
-
const optional = ['supabase_mcp_repair', 'native_capability_repair'];
|
|
1040
|
+
const optional = ['supabase_mcp_repair', 'native_capability_repair', 'sks_menubar'];
|
|
993
1041
|
if (profile === 'full' || profile === 'capabilities')
|
|
994
1042
|
return ['setup', ...required, ...optional];
|
|
995
1043
|
return [...required, ...optional];
|
|
@@ -0,0 +1,458 @@
|
|
|
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.squareLength)
|
|
226
|
+
if let button = statusItem.button {
|
|
227
|
+
configureStatusButton(button)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let menu = NSMenu()
|
|
231
|
+
add(menu, "Use codex-lb", #selector(useCodexLb))
|
|
232
|
+
add(menu, "Use ChatGPT OAuth", #selector(useChatGptOAuth))
|
|
233
|
+
menu.addItem(NSMenuItem.separator())
|
|
234
|
+
add(menu, "Set OpenRouter Key and GLM Profiles", #selector(setOpenRouterKey))
|
|
235
|
+
add(menu, "Fast Check", #selector(fastCheck))
|
|
236
|
+
add(menu, "SKS Version Check", #selector(sksVersionCheck))
|
|
237
|
+
add(menu, "Update SKS Now", #selector(updateSksNow))
|
|
238
|
+
menu.addItem(NSMenuItem.separator())
|
|
239
|
+
add(menu, "Open Codex Settings", #selector(openCodexSettings))
|
|
240
|
+
add(menu, "Restart Codex", #selector(restartCodex))
|
|
241
|
+
menu.addItem(NSMenuItem.separator())
|
|
242
|
+
add(menu, "Quit SKS Menu", #selector(quit))
|
|
243
|
+
statusItem.menu = menu
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
func configureStatusButton(_ button: NSStatusBarButton) {
|
|
247
|
+
if #available(macOS 11.0, *), let image = NSImage(systemSymbolName: "s.circle.fill", accessibilityDescription: "SKS") {
|
|
248
|
+
image.isTemplate = true
|
|
249
|
+
button.image = image
|
|
250
|
+
button.imagePosition = .imageOnly
|
|
251
|
+
button.title = ""
|
|
252
|
+
} else {
|
|
253
|
+
button.title = "S"
|
|
254
|
+
}
|
|
255
|
+
button.toolTip = "SKS - Sneakoscope Codex settings"
|
|
256
|
+
button.setAccessibilityLabel("SKS")
|
|
257
|
+
button.setAccessibilityHelp("Open SKS menu")
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func add(_ menu: NSMenu, _ title: String, _ selector: Selector) {
|
|
261
|
+
let item = NSMenuItem(title: title, action: selector, keyEquivalent: "")
|
|
262
|
+
item.target = self
|
|
263
|
+
menu.addItem(item)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
func runSks(_ args: [String], tail: String = "echo; echo 'SKS command finished. Close this window when ready.'") {
|
|
267
|
+
let quoted = args.map(shellQuote).joined(separator: " ")
|
|
268
|
+
runInTerminal("\\(shellQuote(actionScript)) \\(quoted); \\(tail)")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@objc func useCodexLb() {
|
|
272
|
+
runSks(["codex-lb", "use-codex-lb"])
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@objc func useChatGptOAuth() {
|
|
276
|
+
runSks(["codex-lb", "use-oauth"])
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@objc func setOpenRouterKey() {
|
|
280
|
+
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.'"
|
|
281
|
+
runInTerminal(command)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@objc func fastCheck() {
|
|
285
|
+
runSks(["codex-lb", "fast-check"])
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@objc func sksVersionCheck() {
|
|
289
|
+
let sks = shellQuote(actionScript)
|
|
290
|
+
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.'")
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@objc func updateSksNow() {
|
|
294
|
+
runSks(["update"], tail: "echo; echo 'SKS update finished. Close this window when ready.'")
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@objc func openCodexSettings() {
|
|
298
|
+
runDetached("/usr/bin/open", ["codex://settings"])
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@objc func restartCodex() {
|
|
302
|
+
runInTerminal("/usr/bin/osascript -e 'tell application \\"Codex\\" to quit'; sleep 1; /usr/bin/open -a Codex; echo 'Codex restart requested.'")
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@objc func quit() {
|
|
306
|
+
NSApplication.shared.terminate(nil)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let app = NSApplication.shared
|
|
311
|
+
let delegate = AppDelegate()
|
|
312
|
+
app.delegate = delegate
|
|
313
|
+
app.run()
|
|
314
|
+
`;
|
|
315
|
+
}
|
|
316
|
+
function infoPlistSource() {
|
|
317
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
318
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
319
|
+
<plist version="1.0">
|
|
320
|
+
<dict>
|
|
321
|
+
<key>CFBundleExecutable</key>
|
|
322
|
+
<string>SKSMenuBar</string>
|
|
323
|
+
<key>CFBundleIdentifier</key>
|
|
324
|
+
<string>${LABEL}</string>
|
|
325
|
+
<key>CFBundleName</key>
|
|
326
|
+
<string>SKS Menu Bar</string>
|
|
327
|
+
<key>CFBundlePackageType</key>
|
|
328
|
+
<string>APPL</string>
|
|
329
|
+
<key>CFBundleVersion</key>
|
|
330
|
+
<string>1</string>
|
|
331
|
+
<key>LSUIElement</key>
|
|
332
|
+
<true/>
|
|
333
|
+
</dict>
|
|
334
|
+
</plist>
|
|
335
|
+
`;
|
|
336
|
+
}
|
|
337
|
+
function launchAgentSource(executablePath, installDir) {
|
|
338
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
339
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
340
|
+
<plist version="1.0">
|
|
341
|
+
<dict>
|
|
342
|
+
<key>Label</key>
|
|
343
|
+
<string>${LABEL}</string>
|
|
344
|
+
<key>ProgramArguments</key>
|
|
345
|
+
<array>
|
|
346
|
+
<string>${escapeXml(executablePath)}</string>
|
|
347
|
+
</array>
|
|
348
|
+
<key>RunAtLoad</key>
|
|
349
|
+
<true/>
|
|
350
|
+
<key>KeepAlive</key>
|
|
351
|
+
<true/>
|
|
352
|
+
<key>StandardOutPath</key>
|
|
353
|
+
<string>${escapeXml(path.join(installDir, 'menubar.out.log'))}</string>
|
|
354
|
+
<key>StandardErrorPath</key>
|
|
355
|
+
<string>${escapeXml(path.join(installDir, 'menubar.err.log'))}</string>
|
|
356
|
+
</dict>
|
|
357
|
+
</plist>
|
|
358
|
+
`;
|
|
359
|
+
}
|
|
360
|
+
async function launchWithLaunchctl(input) {
|
|
361
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
362
|
+
const domain = uid === null ? 'gui' : `gui/${uid}`;
|
|
363
|
+
await runProcess(input.launchctl, ['bootout', `${domain}/${LABEL}`], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
|
|
364
|
+
await runProcess(input.launchctl, ['bootout', domain, input.launchAgentPath], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
|
|
365
|
+
await terminateExistingMenuBarProcess(input.executablePath);
|
|
366
|
+
const bootstrap = await runProcess(input.launchctl, ['bootstrap', domain, input.launchAgentPath], {
|
|
367
|
+
timeoutMs: 10_000,
|
|
368
|
+
maxOutputBytes: 32 * 1024
|
|
369
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
370
|
+
if (bootstrap.code === 0) {
|
|
371
|
+
const kickstart = await runProcess(input.launchctl, ['kickstart', '-k', `${domain}/${LABEL}`], {
|
|
372
|
+
timeoutMs: 5_000,
|
|
373
|
+
maxOutputBytes: 32 * 1024
|
|
374
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
375
|
+
if (kickstart.code !== 0) {
|
|
376
|
+
const printed = await waitForLaunchctlRunning(input.launchctl, `${domain}/${LABEL}`);
|
|
377
|
+
if (printed.running) {
|
|
378
|
+
return {
|
|
379
|
+
requested: true,
|
|
380
|
+
method: 'launchctl',
|
|
381
|
+
ok: true,
|
|
382
|
+
bootstrap_code: bootstrap.code,
|
|
383
|
+
kickstart_code: kickstart.code,
|
|
384
|
+
print_code: printed.code,
|
|
385
|
+
error: null
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
requested: true,
|
|
391
|
+
method: 'launchctl',
|
|
392
|
+
ok: kickstart.code === 0,
|
|
393
|
+
bootstrap_code: bootstrap.code,
|
|
394
|
+
kickstart_code: kickstart.code,
|
|
395
|
+
error: kickstart.code === 0 ? null : String(kickstart.stderr || kickstart.stdout || '').trim() || 'launchctl_kickstart_failed'
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (input.open) {
|
|
399
|
+
const opened = await runProcess(input.open, [input.appPath], {
|
|
400
|
+
timeoutMs: 10_000,
|
|
401
|
+
maxOutputBytes: 32 * 1024
|
|
402
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
403
|
+
return {
|
|
404
|
+
requested: true,
|
|
405
|
+
method: 'open-fallback',
|
|
406
|
+
ok: opened.code === 0,
|
|
407
|
+
bootstrap_code: bootstrap.code,
|
|
408
|
+
open_code: opened.code,
|
|
409
|
+
error: opened.code === 0
|
|
410
|
+
? null
|
|
411
|
+
: String(opened.stderr || opened.stdout || bootstrap.stderr || bootstrap.stdout || '').trim() || 'sks_menubar_launch_failed'
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
requested: true,
|
|
416
|
+
method: 'launchctl',
|
|
417
|
+
ok: false,
|
|
418
|
+
bootstrap_code: bootstrap.code,
|
|
419
|
+
error: String(bootstrap.stderr || bootstrap.stdout || '').trim() || 'launchctl_bootstrap_failed'
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
async function waitForLaunchctlRunning(launchctl, service) {
|
|
423
|
+
let lastCode = null;
|
|
424
|
+
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
425
|
+
const printed = await runProcess(launchctl, ['print', service], {
|
|
426
|
+
timeoutMs: 2_000,
|
|
427
|
+
maxOutputBytes: 64 * 1024
|
|
428
|
+
}).catch(() => ({ code: 1, stdout: '', stderr: '' }));
|
|
429
|
+
lastCode = printed.code;
|
|
430
|
+
if (printed.code === 0 && /\bstate = running\b|\bpid = \d+\b/.test(`${printed.stdout || ''}\n${printed.stderr || ''}`)) {
|
|
431
|
+
return { code: printed.code, running: true };
|
|
432
|
+
}
|
|
433
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
434
|
+
}
|
|
435
|
+
return { code: lastCode, running: false };
|
|
436
|
+
}
|
|
437
|
+
async function terminateExistingMenuBarProcess(executablePath) {
|
|
438
|
+
const pkill = await which('pkill').catch(() => null) || await fallbackTool('/usr/bin/pkill');
|
|
439
|
+
if (!pkill)
|
|
440
|
+
return;
|
|
441
|
+
await runProcess(pkill, ['-f', executablePath], { timeoutMs: 5_000, maxOutputBytes: 8 * 1024 }).catch(() => undefined);
|
|
442
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
443
|
+
}
|
|
444
|
+
function swiftString(value) {
|
|
445
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
446
|
+
}
|
|
447
|
+
function shellQuote(value) {
|
|
448
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
449
|
+
}
|
|
450
|
+
function escapeXml(value) {
|
|
451
|
+
return value
|
|
452
|
+
.replace(/&/g, '&')
|
|
453
|
+
.replace(/</g, '<')
|
|
454
|
+
.replace(/>/g, '>')
|
|
455
|
+
.replace(/"/g, '"')
|
|
456
|
+
.replace(/'/g, ''');
|
|
457
|
+
}
|
|
458
|
+
//# sourceMappingURL=sks-menubar.js.map
|
package/dist/core/codex-app.js
CHANGED
|
@@ -457,7 +457,11 @@ function codexNativeSksMenuStatus() {
|
|
|
457
457
|
status: 'unsupported_official_api',
|
|
458
458
|
requested_label: 'SKS',
|
|
459
459
|
requested_position: 'between Window and Help',
|
|
460
|
+
companion_supported: process.platform === 'darwin',
|
|
461
|
+
companion_position: 'right macOS menu bar status item',
|
|
462
|
+
companion_install_command: 'sks doctor --fix',
|
|
460
463
|
alternatives: [
|
|
464
|
+
'sks doctor --fix',
|
|
461
465
|
'sks codex-lb use-codex-lb',
|
|
462
466
|
'sks codex-lb use-oauth',
|
|
463
467
|
'sks codex-app set-openrouter-key --api-key-stdin',
|
|
@@ -559,7 +563,7 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
|
|
|
559
563
|
lines.push('Codex App git actions are enabled for Commit, Push, Commit and Push, and PR flows; SKS hooks treat those app metadata actions as lightweight git UI actions.');
|
|
560
564
|
}
|
|
561
565
|
if (nativeSksMenu?.supported === false) {
|
|
562
|
-
lines.push('
|
|
566
|
+
lines.push('Codex App does not expose a documented third-party API for inserting SKS between Window and Help, so SKS uses a right-side macOS menu bar companion instead. Run `sks doctor --fix` to install or refresh it.');
|
|
563
567
|
}
|
|
564
568
|
if (appInstalled && (!computerUseReady || !browserToolReady)) {
|
|
565
569
|
lines.push('Open Codex App settings and enable recommended MCP/plugin tools. Codex CLI 0.130.0+ remote-control/app-server sessions can pick up config changes live; restart older CLI/TUI sessions.');
|
|
@@ -113,24 +113,29 @@ export async function updateCheckCommand(args = []) {
|
|
|
113
113
|
return printJson(result);
|
|
114
114
|
console.log(`${sksTextLogo()}\n\n${formatSksUpdateCheckText(result)}`);
|
|
115
115
|
}
|
|
116
|
-
export async function updateCommand(sub = '
|
|
117
|
-
|
|
116
|
+
export async function updateCommand(sub = 'now', args = []) {
|
|
117
|
+
let action = String(sub || 'now').toLowerCase();
|
|
118
|
+
let effectiveArgs = args;
|
|
119
|
+
if (action.startsWith('-')) {
|
|
120
|
+
effectiveArgs = [String(sub), ...args];
|
|
121
|
+
action = 'now';
|
|
122
|
+
}
|
|
118
123
|
if (action === 'check' || action === 'status')
|
|
119
|
-
return updateCheckCommand(
|
|
124
|
+
return updateCheckCommand(effectiveArgs);
|
|
120
125
|
if (action !== 'now') {
|
|
121
|
-
console.error('Usage: sks update check|now [--version <version>] [--json] [--dry-run]');
|
|
126
|
+
console.error('Usage: sks update [check|now] [--version <version>] [--json] [--dry-run]');
|
|
122
127
|
process.exitCode = 1;
|
|
123
128
|
return;
|
|
124
129
|
}
|
|
125
130
|
const root = await projectRoot();
|
|
126
131
|
const result = await withSecretPreservationGuard(root, 'update-now', async () => runSksUpdateNow({
|
|
127
|
-
version: valueAfter(
|
|
128
|
-
dryRun: flag(
|
|
132
|
+
version: valueAfter(effectiveArgs, '--version') || valueAfter(effectiveArgs, '-v'),
|
|
133
|
+
dryRun: flag(effectiveArgs, '--dry-run'),
|
|
129
134
|
projectRoot: root,
|
|
130
135
|
timeoutMs: 10 * 60 * 1000,
|
|
131
136
|
maxOutputBytes: 128 * 1024
|
|
132
137
|
}));
|
|
133
|
-
if (flag(
|
|
138
|
+
if (flag(effectiveArgs, '--json'))
|
|
134
139
|
return printJson(result);
|
|
135
140
|
console.log(`${sksTextLogo()}\n`);
|
|
136
141
|
console.log(`SKS update ${result.status}`);
|
|
@@ -144,6 +149,8 @@ export async function updateCommand(sub = 'check', args = []) {
|
|
|
144
149
|
console.log(`New version: ${result.new_version}`);
|
|
145
150
|
if (result.project_receipt)
|
|
146
151
|
console.log(`Migration receipt: ${result.project_receipt.root} (${result.migration_current ? 'current' : 'not current'})`);
|
|
152
|
+
if (result.sks_menubar)
|
|
153
|
+
console.log(`SKS menu bar: ${result.sks_menubar.status}${result.sks_menubar.app_path ? ` (${result.sks_menubar.app_path})` : ''}`);
|
|
147
154
|
for (const stage of result.stages || [])
|
|
148
155
|
console.log(`Stage ${stage.id}: ${stage.ok ? 'ok' : 'failed'} ${stage.status}`);
|
|
149
156
|
if (result.error)
|
|
@@ -89,6 +89,8 @@ function phaseInputFiles(id) {
|
|
|
89
89
|
return ['src/core/doctor/codex-startup-config-repair.ts', '.codex/config.toml'];
|
|
90
90
|
if (id.includes('supabase'))
|
|
91
91
|
return ['src/core/doctor/supabase-mcp-repair.ts', '.codex/config.toml'];
|
|
92
|
+
if (id.includes('menubar'))
|
|
93
|
+
return ['src/core/codex-app/sks-menubar.ts', 'src/commands/doctor.ts', 'package.json'];
|
|
92
94
|
if (id.includes('skill'))
|
|
93
95
|
return ['.agents/skills', 'src/scripts/skill-registry-ledger-check.ts'];
|
|
94
96
|
if (id.includes('native'))
|
|
@@ -60,6 +60,8 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
60
60
|
blockers.add('codex_app_fast_ui_repair_requires_confirmation');
|
|
61
61
|
if (input.codex_app_ui?.fast_selector === 'repaired')
|
|
62
62
|
warnings.add('codex_app_fast_selector_repaired_restart_app_if_needed');
|
|
63
|
+
if (input.sks_menubar?.ok === false)
|
|
64
|
+
warnings.add(`sks_menubar_${input.sks_menubar?.status || 'blocked'}`);
|
|
63
65
|
const codex0138Doctor = input.codex_0138_doctor || null;
|
|
64
66
|
if (codex0138Doctor?.ok === false)
|
|
65
67
|
for (const blocker of normalizeList(codex0138Doctor.blockers))
|
|
@@ -148,6 +150,7 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
148
150
|
codex_app_harness_matrix: codexAppHarness,
|
|
149
151
|
fast_mode_ready: input.fast_mode_ready !== false,
|
|
150
152
|
codex_app_ui: input.codex_app_ui || null,
|
|
153
|
+
sks_menubar: input.sks_menubar || null,
|
|
151
154
|
hooks_ready: input.hooks_ready !== false,
|
|
152
155
|
codex_app_ready: input.codex_app?.ok === true,
|
|
153
156
|
codex_app_required_for_cli: false,
|
|
@@ -205,6 +208,7 @@ function buildRepairReadiness(input = {}) {
|
|
|
205
208
|
add('context7_repair', input.context7_repair, true);
|
|
206
209
|
add('context7_mcp_repair', input.context7_mcp_repair, true);
|
|
207
210
|
add('supabase_mcp_repair', input.supabase_mcp_repair, input.supabase_mcp_repair?.ready_blocking === true);
|
|
211
|
+
add('sks_menubar', input.sks_menubar, false);
|
|
208
212
|
add('command_alias_cleanup', input.command_aliases, true);
|
|
209
213
|
add('native_capability_repair', input.doctor_native_capability, false);
|
|
210
214
|
if (input.doctor_fix_transaction) {
|
package/dist/core/fsx.js
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
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '4.
|
|
8
|
+
export const PACKAGE_VERSION = '4.8.0';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
|
|
2
2
|
export const MANAGED_ASSET_SCHEMA_VERSION = 1;
|
|
3
|
-
export const MANAGED_ASSET_VERSION = '4.
|
|
3
|
+
export const MANAGED_ASSET_VERSION = '4.8.0';
|
|
4
4
|
export const MANAGED_ASSET_MARKER = 'SKS-MANAGED-ASSET';
|
|
5
5
|
export const MANAGED_AGENT_ROLES = Object.freeze([
|
|
6
6
|
role('sks-explorer', 'analysis-scout.toml', 'analysis_scout', 'SKS analysis scout for bounded read/write slices retained for stale Codex agent-role config repair.', 'workspace-write', ['analysis-scout', 'analysis_scout']),
|
package/dist/core/routes.js
CHANGED
|
@@ -699,7 +699,7 @@ export const COMMAND_CATALOG = [
|
|
|
699
699
|
{ name: 'quickstart', usage: 'sks quickstart', description: 'Show the shortest safe setup and verification flow.' },
|
|
700
700
|
{ name: 'bootstrap', usage: 'sks bootstrap [--install-scope global|project] [--local-only] [--json]', description: 'Initialize the current project, install SKS Codex App files/skills, check Context7/Codex App/Zellij, and print ready true/false.' },
|
|
701
701
|
{ name: 'root', usage: 'sks root [--json]', description: 'Show whether SKS is using a project root or the per-user global SKS runtime root.' },
|
|
702
|
-
{ name: 'update', usage: 'sks update check|now [--version <version>] [--json] [--dry-run]', description: 'Check for SKS
|
|
702
|
+
{ name: 'update', usage: 'sks update [check|now] [--version <version>] [--json] [--dry-run]', description: 'Check npm for the latest SKS release and update the global package by default; use `check` for status-only.' },
|
|
703
703
|
{ name: 'deps', usage: 'sks deps check [--json] [--yes]', description: 'Check Node/npm, Codex CLI, and Zellij readiness; pass --yes to repair missing Codex CLI/Zellij tooling when supported.' },
|
|
704
704
|
{ name: 'codex', usage: 'sks codex compatibility|version|doctor|schema|0.142 [--json]', description: 'Check Codex CLI rust-v0.142.0 compatibility, installed version, 0.142 manifest/capability evidence, inherited legacy baselines, and vendored hook schema snapshot freshness.' },
|
|
705
705
|
{ name: 'codex-app', usage: 'sks codex-app [check|glm-profile install|set-openrouter-key --api-key-stdin|product-design|chrome-extension|pat status|remote-control]', description: 'Check Codex App install, GLM/OpenRouter model profile visibility, codex-lb key-entry guidance, Product Design plugin readiness, Codex Chrome Extension web verification readiness, PAT-safe status, first-party MCP/plugin readiness, and Codex CLI 0.130.0+ remote-control availability.' },
|
|
@@ -4,6 +4,7 @@ import { PACKAGE_VERSION, packageRoot, readJson, runProcess, which } from './fsx
|
|
|
4
4
|
import { createRequestedScopeContract } from './safety/requested-scope-contract.js';
|
|
5
5
|
import { guardedPackageInstall, guardContextForRoute } from './safety/mutation-guard.js';
|
|
6
6
|
import { isUpdateMigrationReceiptCurrent, resolveInstalledSksEntrypoint, runPackageLocalDoctor, writeProjectUpdateMigrationReceipt } from './update/update-migration-state.js';
|
|
7
|
+
import { installSksMenuBar } from './codex-app/sks-menubar.js';
|
|
7
8
|
const DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
|
|
8
9
|
export async function runSksUpdateCheck(options = {}) {
|
|
9
10
|
const packageName = options.packageName || 'sneakoscope';
|
|
@@ -164,6 +165,9 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
164
165
|
}).catch(() => null);
|
|
165
166
|
const migrationCurrent = isUpdateMigrationReceiptCurrent(receipt);
|
|
166
167
|
stage('project_receipt', migrationCurrent, migrationCurrent ? 'current' : 'failed', { root: projectReceiptRoot });
|
|
168
|
+
const sksMenuBar = migrationCurrent
|
|
169
|
+
? await installUpdateSksMenuBar({ root: projectReceiptRoot, env, stage })
|
|
170
|
+
: null;
|
|
167
171
|
return buildUpdateNowResult({
|
|
168
172
|
packageName,
|
|
169
173
|
from: check.current,
|
|
@@ -185,19 +189,14 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
185
189
|
newVersionDoctor: null,
|
|
186
190
|
projectReceipt: receipt,
|
|
187
191
|
migrationCurrent,
|
|
192
|
+
sksMenuBar,
|
|
188
193
|
stages,
|
|
189
194
|
error: null
|
|
190
195
|
});
|
|
191
196
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
env,
|
|
196
|
-
timeoutMs: 15_000,
|
|
197
|
-
maxOutputBytes: 32 * 1024
|
|
198
|
-
});
|
|
199
|
-
stage('old_version_doctor_preflight', oldVersionDoctor.ok, oldVersionDoctor.status, { entrypoint: oldVersionDoctor.entrypoint, exit_code: oldVersionDoctor.exit_code });
|
|
200
|
-
if (!oldVersionDoctor.ok && env.SKS_UPDATE_SKIP_OLD_DOCTOR_PREFLIGHT !== '1') {
|
|
197
|
+
if (options.dryRun) {
|
|
198
|
+
stage('old_version_doctor_preflight', true, 'skipped_dry_run', { reason: 'dry_run_does_not_run_doctor_fix' });
|
|
199
|
+
stage('npm_install', true, 'dry_run', { command });
|
|
201
200
|
return buildUpdateNowResult({
|
|
202
201
|
packageName,
|
|
203
202
|
from: check.current,
|
|
@@ -210,21 +209,32 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
210
209
|
cwd,
|
|
211
210
|
registry,
|
|
212
211
|
globalRoot,
|
|
213
|
-
status: '
|
|
214
|
-
ok:
|
|
212
|
+
status: 'dry_run',
|
|
213
|
+
ok: true,
|
|
215
214
|
installCode: null,
|
|
216
|
-
oldVersionDoctor,
|
|
215
|
+
oldVersionDoctor: null,
|
|
217
216
|
newBinary: null,
|
|
218
217
|
newVersion: null,
|
|
219
218
|
newVersionDoctor: null,
|
|
220
219
|
projectReceipt: null,
|
|
221
220
|
migrationCurrent: false,
|
|
222
221
|
stages,
|
|
223
|
-
error:
|
|
222
|
+
error: null
|
|
224
223
|
});
|
|
225
224
|
}
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
const oldDoctorTimeoutOverride = Number.parseInt(env.SKS_UPDATE_OLD_DOCTOR_TIMEOUT_MS || '', 10);
|
|
226
|
+
const oldDoctorTimeoutMs = Number.isFinite(oldDoctorTimeoutOverride) && oldDoctorTimeoutOverride > 0
|
|
227
|
+
? oldDoctorTimeoutOverride
|
|
228
|
+
: 60_000;
|
|
229
|
+
const oldVersionDoctor = await runPackageLocalDoctor({
|
|
230
|
+
root: projectReceiptRoot,
|
|
231
|
+
args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', path.join(projectReceiptRoot, '.sneakoscope', 'update', `old-version-doctor-${Date.now()}.json`)],
|
|
232
|
+
env,
|
|
233
|
+
timeoutMs: oldDoctorTimeoutMs,
|
|
234
|
+
maxOutputBytes: 32 * 1024
|
|
235
|
+
});
|
|
236
|
+
stage('old_version_doctor_preflight', oldVersionDoctor.ok, oldVersionDoctor.status, { entrypoint: oldVersionDoctor.entrypoint, exit_code: oldVersionDoctor.exit_code, timeout_ms: oldDoctorTimeoutMs });
|
|
237
|
+
if (!oldVersionDoctor.ok && env.SKS_UPDATE_SKIP_OLD_DOCTOR_PREFLIGHT !== '1') {
|
|
228
238
|
return buildUpdateNowResult({
|
|
229
239
|
packageName,
|
|
230
240
|
from: check.current,
|
|
@@ -237,8 +247,8 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
237
247
|
cwd,
|
|
238
248
|
registry,
|
|
239
249
|
globalRoot,
|
|
240
|
-
status: '
|
|
241
|
-
ok:
|
|
250
|
+
status: 'failed',
|
|
251
|
+
ok: false,
|
|
242
252
|
installCode: null,
|
|
243
253
|
oldVersionDoctor,
|
|
244
254
|
newBinary: null,
|
|
@@ -247,7 +257,7 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
247
257
|
projectReceipt: null,
|
|
248
258
|
migrationCurrent: false,
|
|
249
259
|
stages,
|
|
250
|
-
error:
|
|
260
|
+
error: oldVersionDoctor.error || 'old-version Doctor preflight failed'
|
|
251
261
|
});
|
|
252
262
|
}
|
|
253
263
|
const mutationLedgerRoot = env.SKS_MUTATION_LEDGER_ROOT || packageRoot();
|
|
@@ -277,6 +287,7 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
277
287
|
let newVersionDoctor = null;
|
|
278
288
|
let projectReceipt = null;
|
|
279
289
|
let migrationCurrent = false;
|
|
290
|
+
let sksMenuBar = null;
|
|
280
291
|
if (installOk) {
|
|
281
292
|
newBinary = await resolveInstalledSksEntrypoint({ packageName, globalRoot, env });
|
|
282
293
|
stage('resolve_new_package_local_binary', Boolean(newBinary), newBinary ? 'resolved' : 'missing', { new_binary: newBinary });
|
|
@@ -310,6 +321,8 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
310
321
|
}).catch(() => null);
|
|
311
322
|
migrationCurrent = isUpdateMigrationReceiptCurrent(projectReceipt);
|
|
312
323
|
stage('project_receipt', migrationCurrent, migrationCurrent ? 'current' : 'failed', { root: projectReceiptRoot });
|
|
324
|
+
if (migrationCurrent)
|
|
325
|
+
sksMenuBar = await installUpdateSksMenuBar({ root: projectReceiptRoot, env, stage });
|
|
313
326
|
}
|
|
314
327
|
}
|
|
315
328
|
const ok = installOk && Boolean(newBinary) && newVersionDoctor?.ok === true && migrationCurrent;
|
|
@@ -334,6 +347,7 @@ export async function runSksUpdateNow(options = {}) {
|
|
|
334
347
|
newVersionDoctor,
|
|
335
348
|
projectReceipt,
|
|
336
349
|
migrationCurrent,
|
|
350
|
+
sksMenuBar,
|
|
337
351
|
stages,
|
|
338
352
|
error: ok ? null : updateNowError(install, newBinary, newVersionDoctor, migrationCurrent)
|
|
339
353
|
});
|
|
@@ -500,10 +514,45 @@ function buildUpdateNowResult(input) {
|
|
|
500
514
|
new_version_doctor: input.newVersionDoctor,
|
|
501
515
|
project_receipt: input.projectReceipt,
|
|
502
516
|
migration_current: input.migrationCurrent,
|
|
517
|
+
sks_menubar: input.sksMenuBar || null,
|
|
503
518
|
stages: input.stages,
|
|
504
519
|
error: input.error
|
|
505
520
|
};
|
|
506
521
|
}
|
|
522
|
+
async function installUpdateSksMenuBar(input) {
|
|
523
|
+
if (input.env.SKS_UPDATE_SKIP_SKS_MENUBAR === '1') {
|
|
524
|
+
input.stage('sks_menubar', true, 'skipped', { reason: 'SKS_UPDATE_SKIP_SKS_MENUBAR=1' });
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
const result = await installSksMenuBar({
|
|
528
|
+
root: input.root,
|
|
529
|
+
apply: true,
|
|
530
|
+
launch: true,
|
|
531
|
+
env: input.env
|
|
532
|
+
}).catch((err) => ({
|
|
533
|
+
schema: 'sks.codex-app-sks-menubar.v1',
|
|
534
|
+
ok: false,
|
|
535
|
+
apply: true,
|
|
536
|
+
status: 'blocked',
|
|
537
|
+
platform: process.platform,
|
|
538
|
+
app_path: null,
|
|
539
|
+
executable_path: null,
|
|
540
|
+
launch_agent_path: null,
|
|
541
|
+
action_script_path: null,
|
|
542
|
+
report_path: path.join(input.root, '.sneakoscope', 'reports', 'sks-menubar.json'),
|
|
543
|
+
menu_items: [],
|
|
544
|
+
actions: [],
|
|
545
|
+
launch: { requested: true, method: 'none', ok: false, error: err?.message || String(err) },
|
|
546
|
+
blockers: [err?.message || String(err)],
|
|
547
|
+
warnings: []
|
|
548
|
+
}));
|
|
549
|
+
input.stage('sks_menubar', result.ok !== false, result.status, {
|
|
550
|
+
app_path: result.app_path,
|
|
551
|
+
launch_agent_path: result.launch_agent_path,
|
|
552
|
+
launch: result.launch
|
|
553
|
+
});
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
507
556
|
async function detectNpmGlobalRoot(npmBin, env, opts = {}) {
|
|
508
557
|
const result = await runProcess(npmBin, ['root', '--global', '--silent'], {
|
|
509
558
|
env,
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '4.
|
|
1
|
+
export const PACKAGE_VERSION = '4.8.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { installSksMenuBar } from '../core/codex-app/sks-menubar.js';
|
|
5
|
+
const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-menubar-check-'));
|
|
6
|
+
const result = await installSksMenuBar({
|
|
7
|
+
apply: true,
|
|
8
|
+
launch: false,
|
|
9
|
+
home: temp,
|
|
10
|
+
root: temp,
|
|
11
|
+
sksEntry: path.join(process.cwd(), 'dist', 'bin', 'sks.js')
|
|
12
|
+
});
|
|
13
|
+
const executableExists = result.executable_path ? await exists(result.executable_path) : false;
|
|
14
|
+
const launchAgentExists = result.launch_agent_path ? await exists(result.launch_agent_path) : false;
|
|
15
|
+
const actionScriptExists = result.action_script_path ? await exists(result.action_script_path) : false;
|
|
16
|
+
const generatedSourcePath = result.app_path ? path.join(path.dirname(result.app_path), 'SKSMenuBar.swift') : null;
|
|
17
|
+
const generatedSource = generatedSourcePath ? await fs.readFile(generatedSourcePath, 'utf8').catch(() => '') : '';
|
|
18
|
+
const hasVisibleIconSource = generatedSource.includes('NSStatusItem.squareLength')
|
|
19
|
+
&& generatedSource.includes('s.circle.fill')
|
|
20
|
+
&& generatedSource.includes('configureStatusButton');
|
|
21
|
+
const expectedMenuItems = [
|
|
22
|
+
'Use codex-lb',
|
|
23
|
+
'Use ChatGPT OAuth',
|
|
24
|
+
'Set OpenRouter Key and GLM Profiles',
|
|
25
|
+
'Fast Check',
|
|
26
|
+
'SKS Version Check',
|
|
27
|
+
'Update SKS Now'
|
|
28
|
+
];
|
|
29
|
+
const missingExpectedItems = expectedMenuItems.filter((item) => !result.menu_items.includes(item));
|
|
30
|
+
const hasExpectedItems = missingExpectedItems.length === 0;
|
|
31
|
+
const ok = process.platform === 'darwin'
|
|
32
|
+
? result.ok === true
|
|
33
|
+
&& result.status === 'installed_launch_skipped'
|
|
34
|
+
&& executableExists
|
|
35
|
+
&& launchAgentExists
|
|
36
|
+
&& actionScriptExists
|
|
37
|
+
&& hasExpectedItems
|
|
38
|
+
&& hasVisibleIconSource
|
|
39
|
+
: result.ok === true && result.status === 'unsupported_platform';
|
|
40
|
+
const report = {
|
|
41
|
+
schema: 'sks.sks-menubar-install-check.v1',
|
|
42
|
+
ok,
|
|
43
|
+
temp,
|
|
44
|
+
result,
|
|
45
|
+
executable_exists: executableExists,
|
|
46
|
+
launch_agent_exists: launchAgentExists,
|
|
47
|
+
action_script_exists: actionScriptExists,
|
|
48
|
+
generated_source_path: generatedSourcePath,
|
|
49
|
+
has_visible_icon_source: hasVisibleIconSource,
|
|
50
|
+
expected_menu_items: expectedMenuItems,
|
|
51
|
+
missing_expected_items: missingExpectedItems,
|
|
52
|
+
has_expected_items: hasExpectedItems,
|
|
53
|
+
blockers: ok ? [] : ['sks_menubar_install_check_failed']
|
|
54
|
+
};
|
|
55
|
+
console.log(JSON.stringify(report, null, 2));
|
|
56
|
+
if (!ok)
|
|
57
|
+
process.exit(1);
|
|
58
|
+
async function exists(file) {
|
|
59
|
+
try {
|
|
60
|
+
await fs.access(file);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=sks-menubar-install-check.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
function assertGate(ok, message, detail) {
|
|
3
|
+
if (!ok) {
|
|
4
|
+
console.error(JSON.stringify({ schema: 'sks.update-default-command-check.v1', ok: false, message, detail }, null, 2));
|
|
5
|
+
process.exit(1);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
const registry = fs.readFileSync('src/cli/command-registry.ts', 'utf8');
|
|
9
|
+
const basicCli = fs.readFileSync('src/core/commands/basic-cli.ts', 'utf8');
|
|
10
|
+
const update = fs.readFileSync('src/core/update-check.ts', 'utf8');
|
|
11
|
+
const routes = fs.readFileSync('src/core/routes.ts', 'utf8');
|
|
12
|
+
assertGate(registry.includes("subcommand(() => import(basicModule), 'updateCommand', 'dist/core/commands/basic-cli.js', 'now')"), 'bare sks update must default to update now');
|
|
13
|
+
assertGate(basicCli.includes("export async function updateCommand(sub: any = 'now'"), 'updateCommand default must be now');
|
|
14
|
+
assertGate(basicCli.includes("action.startsWith('-')") && basicCli.includes('effectiveArgs = [String(sub), ...args]'), 'sks update --json/--dry-run must be treated as update now with flags');
|
|
15
|
+
assertGate(update.includes('installSksMenuBar') && update.includes('sks_menubar'), 'update now must refresh the SKS menu bar companion stage');
|
|
16
|
+
assertGate(routes.includes('sks update [check|now]'), 'command catalog must document bare sks update as the default update path');
|
|
17
|
+
console.log(JSON.stringify({
|
|
18
|
+
schema: 'sks.update-default-command-check.v1',
|
|
19
|
+
ok: true,
|
|
20
|
+
checks: [
|
|
21
|
+
'registry_default_now',
|
|
22
|
+
'basic_cli_default_now',
|
|
23
|
+
'flag_first_default_now',
|
|
24
|
+
'update_menubar_stage',
|
|
25
|
+
'command_catalog_usage'
|
|
26
|
+
]
|
|
27
|
+
}, null, 2));
|
|
28
|
+
//# sourceMappingURL=update-default-command-check.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ใ
ใ
ใ
",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.8.0",
|
|
5
5
|
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -168,6 +168,8 @@
|
|
|
168
168
|
"codex:0.135-compat:require-real": "node ./dist/scripts/codex-0-135-compat-check.js --require-real",
|
|
169
169
|
"doctor:codex-doctor-parity": "node ./dist/scripts/doctor-codex-doctor-parity-check.js",
|
|
170
170
|
"doctor:codex-doctor-parity:actual": "node ./dist/scripts/doctor-codex-doctor-parity-check.js --actual-codex",
|
|
171
|
+
"doctor:sks-menubar": "node ./dist/scripts/sks-menubar-install-check.js",
|
|
172
|
+
"update:default-command": "node ./dist/scripts/update-default-command-check.js",
|
|
171
173
|
"codex:permission-profiles": "node ./dist/scripts/codex-permission-profiles-check.js",
|
|
172
174
|
"codex:legacy-profile-consumers-removed": "node ./dist/scripts/codex-legacy-profile-consumers-removed-check.js",
|
|
173
175
|
"terminal:keyboard-enhancement-safety": "node ./dist/scripts/terminal-keyboard-enhancement-safety-check.js",
|