sneakoscope 8.0.0 → 8.0.2
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 +13 -2
- package/config/skills-hash-ledger.v1.json +63 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +14 -3
- package/dist/bin/sks-dispatch.js +1 -1
- package/dist/cli/codex-lb-setup-warning-output.js +17 -0
- package/dist/cli/command-manifest-lite.js +2 -13
- package/dist/cli/install-helpers-codex-lb-config.js +475 -8
- package/dist/cli/install-helpers-install-support.js +1 -1
- package/dist/cli/install-helpers.js +764 -68
- package/dist/cli/router.js +5 -2
- package/dist/commands/codex-lb.js +77 -47
- package/dist/commands/doctor-profile.js +1 -0
- package/dist/commands/doctor.js +165 -58
- package/dist/config/skills-manifest.json +193 -97
- package/dist/core/codex/codex-config-guard.js +70 -16
- package/dist/core/codex/oauth-callback-port-diagnostic.js +121 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -2
- package/dist/core/codex-app/menubar/constants.js +3 -0
- package/dist/core/codex-app/menubar/installer.js +32 -9
- package/dist/core/codex-app/menubar/launch-agent.js +58 -6
- package/dist/core/codex-app/menubar/migration.js +17 -6
- package/dist/core/codex-app/menubar/status.js +15 -3
- package/dist/core/codex-app.js +15 -20
- package/dist/core/codex-control/codex-lb-launch-recovery.js +26 -0
- package/dist/core/codex-lb/capability-runner.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +52 -41
- package/dist/core/codex-lb/codex-lb-setup.js +85 -8
- package/dist/core/codex-lb/desktop-center-credentials.js +474 -39
- package/dist/core/codex-lb/desktop-controller.js +118 -4
- package/dist/core/codex-lb/legacy-migration.js +6 -3
- package/dist/core/codex-native/codex-native-feature-broker.js +5 -1
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/codex-native/managed-skill-generation-lock.js +13 -0
- package/dist/core/codex-native/sks-skill-paths.js +97 -10
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +1 -0
- package/dist/core/commands/basic-cli.js +54 -4
- package/dist/core/commands/mad-sks-command.js +1 -1
- package/dist/core/commands/menubar-command.js +24 -8
- package/dist/core/commands/naruto-command.js +42 -217
- package/dist/core/doctor/codex-config-syntax-repair.js +368 -0
- package/dist/core/doctor/doctor-native-capability-repair.js +12 -5
- package/dist/core/doctor/doctor-readiness-matrix.js +51 -4
- package/dist/core/doctor/skill-legacy-surface.js +0 -1
- package/dist/core/doctor/sks-menubar-doctor.js +21 -0
- package/dist/core/fsx.js +7 -14
- package/dist/core/hooks-runtime/hook-context.js +30 -16
- package/dist/core/hooks-runtime/managed-skill-admission.js +17 -0
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +891 -30
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +99 -6
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +3 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-evidence.js +1 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-guards.js +4 -4
- package/dist/core/hooks-runtime/subagent-skill-availability.js +9 -7
- package/dist/core/hooks-runtime/subagent-stop-hook.js +19 -0
- package/dist/core/hooks-runtime.js +41 -14
- package/dist/core/init/skills.js +270 -11
- package/dist/core/mission.js +20 -0
- package/dist/core/naruto/hardware-capacity-probe.js +3 -1
- package/dist/core/naruto/naruto-concurrency-governor.js +9 -3
- package/dist/core/pipeline-internals/runtime-core.js +68 -9
- package/dist/core/pipeline-internals/runtime-gates.js +10 -2
- package/dist/core/preflight/parallel-preflight-engine.js +18 -4
- package/dist/core/release/package-dist-snapshot.js +50 -8
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/command-contract/contracts.js +10 -13
- package/dist/core/safety/command-contract/json-schema.js +38 -1
- package/dist/core/subagents/agent-catalog.js +37 -13
- package/dist/core/subagents/model-policy.js +14 -9
- package/dist/core/subagents/naruto-command-args.js +237 -0
- package/dist/core/subagents/naruto-command-input-contract.js +123 -0
- package/dist/core/subagents/naruto-help-contract.js +35 -2
- package/dist/core/subagents/naruto-launch-diagnostics.js +35 -0
- package/dist/core/subagents/official-subagent-config.js +30 -3
- package/dist/core/subagents/official-subagent-preparation.js +30 -5
- package/dist/core/subagents/official-subagent-prompt.js +4 -3
- package/dist/core/subagents/official-subagent-runner.js +12 -0
- package/dist/core/subagents/subagent-evidence.js +10 -0
- package/dist/core/subagents/thread-budget.js +1 -1
- package/dist/core/subagents/wave-lifecycle.js +41 -11
- package/dist/core/subagents/wave-parent-guidance.js +10 -3
- package/dist/core/update/update-migration-state.js +694 -63
- package/dist/core/update/update-operation.js +498 -32
- package/dist/core/update-check.js +990 -509
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +1 -0
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -1
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +1 -1
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +127 -12
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +7 -2
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +287 -84
- package/dist/native/sks-menubar/Sources/ProcessExecutionState.swift +45 -0
- package/dist/native/sks-menubar/Sources/ProcessIdentityGuard.swift +196 -0
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +169 -0
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +23 -5
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +113 -24
- package/dist/native/sks-menubar/Sources/main.swift +12 -0
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +14 -0
- package/dist/native/sks-menubar/Tests/OperationCoordinatorTests.swift +174 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +4 -0
- package/dist/scripts/current-surface-update-e2e-check.js +24 -5
- package/dist/scripts/lib/ensure-dist-fresh.js +12 -8
- package/dist/scripts/mutation-callsite-analysis.js +381 -0
- package/dist/scripts/mutation-callsite-coverage-check.js +127 -62
- package/dist/scripts/official-subagent-workflow-check.js +1 -1
- package/dist/scripts/skills-manifest-continuity-check.js +19 -1
- package/package.json +5 -3
- package/runtime-required-scripts.json +5 -0
- package/scripts/build-clean-atomic.mjs +323 -0
package/dist/core/codex-app.js
CHANGED
|
@@ -946,19 +946,12 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
946
946
|
&& hasTomlString(codexLbProvider, 'env_key', 'CODEX_LB_API_KEY')
|
|
947
947
|
&& hasTomlBoolean(codexLbProvider, 'supports_websockets', true)
|
|
948
948
|
&& hasTomlBoolean(codexLbProvider, 'requires_openai_auth', false);
|
|
949
|
-
const codexLbCompatProviderContractOk = codexLbProviderPresent
|
|
950
|
-
&& hasTomlString(codexLbProvider, 'name', 'OpenAI')
|
|
951
|
-
&& hasTomlString(codexLbProvider, 'wire_api', 'responses')
|
|
952
|
-
&& hasTomlBoolean(codexLbProvider, 'supports_websockets', true)
|
|
953
|
-
&& hasTomlBoolean(codexLbProvider, 'requires_openai_auth', true)
|
|
954
|
-
&& !hasTomlKey(codexLbProvider, 'env_key')
|
|
955
|
-
&& hasCodexLbGatewayEnvHeader(codexLbProvider);
|
|
956
949
|
const codexLbSelectedDefault = /(?:^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevelToml(globalConfig));
|
|
957
950
|
const codexLbDesktopMode = opts.codexLbDesktopMode
|
|
958
951
|
|| inferCodexLbDesktopModeForUi(globalConfig);
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
952
|
+
const retiredCodexLbCompatConfigured = codexLbDesktopMode === 'desktop-dual-auth-compat';
|
|
953
|
+
const codexLbProviderContractOk = !retiredCodexLbCompatConfigured
|
|
954
|
+
&& codexLbCliProviderContractOk;
|
|
962
955
|
const metadata = await readJsonIfExists(opts.codexLbMetadataPath || codexLbMetadataPath(home));
|
|
963
956
|
const gatewayAuthTransport = normalizeCodexLbGatewayAuthTransportForUi(opts.codexLbGatewayAuthTransport || metadata?.gateway_auth_transport);
|
|
964
957
|
const localCatalogBindingAllowed = shouldBindLocalModelCatalog(codexLbDesktopMode);
|
|
@@ -1009,7 +1002,11 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
1009
1002
|
];
|
|
1010
1003
|
const codexLbBlockers = [
|
|
1011
1004
|
...(codexLbProviderPresent ? [] : ['codex_lb_provider_missing']),
|
|
1012
|
-
...(
|
|
1005
|
+
...(retiredCodexLbCompatConfigured
|
|
1006
|
+
? ['desktop_dual_auth_compat_unavailable']
|
|
1007
|
+
: codexLbProviderPresent && !codexLbProviderContractOk
|
|
1008
|
+
? ['codex_lb_provider_contract_drift']
|
|
1009
|
+
: []),
|
|
1013
1010
|
...(codexLbApiKeySource !== 'missing' ? [] : ['codex_lb_api_key_missing']),
|
|
1014
1011
|
...(codexLbBaseUrlSource !== 'missing' ? [] : ['codex_lb_base_url_missing']),
|
|
1015
1012
|
...(codexLbDesktopMode === 'desktop-native-bridge' && managedCatalogConfigured
|
|
@@ -1050,7 +1047,11 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
1050
1047
|
const installCommand = 'sks codex-app use-openrouter --model z-ai/glm-5.2';
|
|
1051
1048
|
const setupCommand = 'sks codex-lb setup --host <domain> --api-key-stdin --yes';
|
|
1052
1049
|
const setKeyCommand = 'sks codex-lb set-key --api-key-stdin';
|
|
1053
|
-
const desktopPickerEvidence = opts.desktopPickerEvidence
|
|
1050
|
+
const desktopPickerEvidence = opts.desktopPickerEvidence
|
|
1051
|
+
|| opts.codexLbCapabilityReport?.model_picker
|
|
1052
|
+
|| opts.codexLbCapabilityReport?.capabilities?.model_picker
|
|
1053
|
+
|| opts.codexLbStatus?.capabilities?.model_picker
|
|
1054
|
+
|| null;
|
|
1054
1055
|
const desktopPickerVerified = desktopPickerEvidence?.state === 'verified'
|
|
1055
1056
|
|| desktopPickerEvidence?.verified === true;
|
|
1056
1057
|
const selectedProviderConfigured = selectedProviderBlockers.length === 0;
|
|
@@ -1110,8 +1111,8 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
1110
1111
|
key_entry_visible: true,
|
|
1111
1112
|
provider_present: codexLbProviderPresent,
|
|
1112
1113
|
provider_contract_ok: codexLbProviderContractOk,
|
|
1113
|
-
provider_contract:
|
|
1114
|
-
? 'desktop-dual-auth-compat'
|
|
1114
|
+
provider_contract: retiredCodexLbCompatConfigured
|
|
1115
|
+
? 'retired-desktop-dual-auth-compat'
|
|
1115
1116
|
: 'cli-provider',
|
|
1116
1117
|
selected_default: codexLbSelectedDefault,
|
|
1117
1118
|
key_present: codexLbApiKeySource !== 'missing',
|
|
@@ -1187,12 +1188,6 @@ function normalizeCodexLbGatewayAuthTransportForUi(value) {
|
|
|
1187
1188
|
? 'authorization-bearer-compat'
|
|
1188
1189
|
: 'x-codex-lb-api-key';
|
|
1189
1190
|
}
|
|
1190
|
-
function hasTomlKey(text, key) {
|
|
1191
|
-
return new RegExp(`(?:^|\\n)\\s*${escapeRegExp(key)}\\s*=`).test(text);
|
|
1192
|
-
}
|
|
1193
|
-
function hasCodexLbGatewayEnvHeader(text) {
|
|
1194
|
-
return /(?:^|\n)\s*env_http_headers\s*=\s*\{[^}\n]*"X-Codex-LB-API-Key"\s*=\s*"CODEX_LB_API_KEY"[^}\n]*\}\s*(?:#.*)?(?=\n|$)/.test(text);
|
|
1195
|
-
}
|
|
1196
1191
|
async function findDefaultPluginSource(plugin, { home, configText }) {
|
|
1197
1192
|
const cached = await findPluginCache(plugin.name, { home });
|
|
1198
1193
|
if (cached)
|
|
@@ -69,6 +69,12 @@ export async function inspectCodexLbCliLaunchRecovery(input) {
|
|
|
69
69
|
if (selectedProvider !== 'codex-lb') {
|
|
70
70
|
return codexLbToolOutputRecoveryNotSelected();
|
|
71
71
|
}
|
|
72
|
+
const codexProcessSelectsCodexLb = overrides.modelProvider === 'codex-lb'
|
|
73
|
+
|| profileConfig.modelProvider === 'codex-lb'
|
|
74
|
+
|| userConfig.modelProvider === 'codex-lb';
|
|
75
|
+
if (environmentSelectsCodexLb && !codexProcessSelectsCodexLb) {
|
|
76
|
+
return environmentSelectionNotForwardedBlocked();
|
|
77
|
+
}
|
|
72
78
|
const baseUrl = overrides.baseUrl
|
|
73
79
|
?? profileConfig.baseUrl
|
|
74
80
|
?? userConfig.baseUrl
|
|
@@ -124,6 +130,13 @@ export function codexLbRecoveryBlockedProcessResult(probe) {
|
|
|
124
130
|
};
|
|
125
131
|
}
|
|
126
132
|
export function codexLbRecoveryBlockedMessage(probe) {
|
|
133
|
+
if (probe.blockers.includes('codex_lb_launch_selection_not_forwarded')) {
|
|
134
|
+
return [
|
|
135
|
+
'Codex launch blocked: codex-lb was requested by the SKS environment but was not selected in the Codex process.',
|
|
136
|
+
...probe.blockers.map((blocker) => `blocker: ${blocker}`),
|
|
137
|
+
...probe.operator_actions
|
|
138
|
+
].join('\n');
|
|
139
|
+
}
|
|
127
140
|
return [
|
|
128
141
|
'Codex launch blocked: selected codex-lb interrupted tool-output recovery is not verified.',
|
|
129
142
|
...probe.blockers.map((blocker) => `blocker: ${blocker}`),
|
|
@@ -291,3 +304,16 @@ function projectProviderConfigBlocked() {
|
|
|
291
304
|
]
|
|
292
305
|
};
|
|
293
306
|
}
|
|
307
|
+
function environmentSelectionNotForwardedBlocked() {
|
|
308
|
+
const base = codexLbToolOutputRecoveryNotChecked(true);
|
|
309
|
+
return {
|
|
310
|
+
...base,
|
|
311
|
+
status: 'version_unverified',
|
|
312
|
+
blockers: ['codex_lb_launch_selection_not_forwarded'],
|
|
313
|
+
warnings: ['sks_provider_environment_does_not_select_the_codex_process_provider'],
|
|
314
|
+
operator_actions: [
|
|
315
|
+
`Launch Codex with \`--config model_provider='"codex-lb"'\`, or select codex-lb in the active Codex profile.`,
|
|
316
|
+
'Rerun `sks codex-lb status` before launching Codex.'
|
|
317
|
+
]
|
|
318
|
+
};
|
|
319
|
+
}
|
|
@@ -165,6 +165,7 @@ export function shapeCodexLbDesktopCapabilityStatus(report) {
|
|
|
165
165
|
configured: report.configured,
|
|
166
166
|
oauth_preserved: report.oauth_preserved,
|
|
167
167
|
gateway_auth_transport: String(report.gateway_auth_transport.evidence.configured_gateway_auth_transport || 'unknown'),
|
|
168
|
+
model_picker: report.model_picker,
|
|
168
169
|
verified: entries.filter(([, value]) => value.state === 'verified').map(([key]) => key),
|
|
169
170
|
available_unverified: entries.filter(([, value]) => value.state === 'available_unverified').map(([key]) => key),
|
|
170
171
|
blocked: Object.fromEntries(entries
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import { exists, readJson, readText
|
|
3
|
+
import { exists, readJson, readText } from '../fsx.js';
|
|
4
4
|
import { escapeRegExp } from '../text/regex.js';
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
let a=CommandLine.arguments[1],s=CommandLine.arguments[2]
|
|
8
|
-
guard let k=String(data:FileHandle.standardInput.readDataToEndOfFile(),encoding:.utf8)?.trimmingCharacters(in:.whitespacesAndNewlines),!k.isEmpty else{exit(64)}
|
|
9
|
-
let q:[String:Any]=[kSecClass as String:kSecClassGenericPassword,kSecAttrAccount as String:a,kSecAttrService as String:s]
|
|
10
|
-
let v:[String:Any]=[kSecValueData as String:Data(k.utf8)]
|
|
11
|
-
var r=SecItemUpdate(q as CFDictionary,v as CFDictionary)
|
|
12
|
-
if r==errSecItemNotFound{var n=q;n[kSecValueData as String]=Data(k.utf8);r=SecItemAdd(n as CFDictionary,nil)}
|
|
13
|
-
if r != errSecSuccess{FileHandle.standardError.write(Data("keychain_status=\\(r)\\n".utf8));exit(1)}`;
|
|
5
|
+
export const CODEX_LB_SECURE_KEYCHAIN_SERVICE = 'com.sneakoscope.codex-lb.api-key.v2';
|
|
6
|
+
export const CODEX_LB_LEGACY_KEYCHAIN_SERVICE = 'sks-codex-lb';
|
|
14
7
|
export function codexLbEnvPath(home = process.env.HOME || os.homedir()) {
|
|
15
8
|
return path.join(String(home || os.homedir()), '.codex', 'sks-codex-lb.env');
|
|
16
9
|
}
|
|
@@ -161,14 +154,16 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
161
154
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
162
155
|
const metadataPath = opts.metadataPath || codexLbMetadataPath(home);
|
|
163
156
|
const envPaths = [opts.envPath || codexLbEnvPath(home)];
|
|
164
|
-
const sourcePriority = ['env-file', '
|
|
157
|
+
const sourcePriority = ['env-file', 'process.env'];
|
|
165
158
|
if (opts.allowProjectSecrets)
|
|
166
159
|
sourcePriority.push('project-local');
|
|
167
160
|
const processEnv = pickEnv(opts.processEnv || process.env);
|
|
168
161
|
const envFile = await readEnvFile(envPaths[0]);
|
|
169
|
-
const keychain = await readMacKeychain(opts);
|
|
170
162
|
const projectLocal = opts.allowProjectSecrets ? await readEnvFile(path.join(opts.root || process.cwd(), '.env')) : {};
|
|
171
163
|
const metadata = await readCodexLbCredentialMetadata(metadataPath);
|
|
164
|
+
const keychain = await keychainReadIsNeeded(opts, metadata, [envFile.apiKey, processEnv.apiKey, projectLocal.apiKey])
|
|
165
|
+
? await readMacKeychain(opts)
|
|
166
|
+
: { checked: false, available: false, status: 'skipped_not_needed', apiKey: '' };
|
|
172
167
|
const apiKeyCandidates = [
|
|
173
168
|
{ source: 'env-file', apiKey: envFile.apiKey },
|
|
174
169
|
{ source: 'keychain', apiKey: keychain.apiKey },
|
|
@@ -192,12 +187,20 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
192
187
|
apiKeySha256,
|
|
193
188
|
configuredBaseUrl
|
|
194
189
|
});
|
|
190
|
+
const keychainBlockers = keychain.status === 'keychain_read_failed'
|
|
191
|
+
? ['codex_lb_keychain_read_failed']
|
|
192
|
+
: keychain.status === 'legacy_keychain_migration_required'
|
|
193
|
+
? ['codex_lb_legacy_keychain_migration_required']
|
|
194
|
+
: keychain.status === 'keychain_acl_helper_unavailable'
|
|
195
|
+
? ['codex_lb_keychain_acl_helper_unavailable']
|
|
196
|
+
: [];
|
|
195
197
|
const baseUrl = binding.present && metadata.baseUrl ? metadata.baseUrl : configuredBaseUrl;
|
|
196
|
-
const apiKeyUsable = Boolean(apiKey) && binding.blockers.length === 0;
|
|
198
|
+
const apiKeyUsable = Boolean(apiKey) && binding.blockers.length === 0 && keychainBlockers.length === 0;
|
|
197
199
|
const missing = [
|
|
198
200
|
...(apiKey ? [] : ['CODEX_LB_API_KEY']),
|
|
199
201
|
...(baseUrl ? [] : ['CODEX_LB_BASE_URL']),
|
|
200
|
-
...(binding.blockers.length ? ['CODEX_LB_CREDENTIAL_BINDING'] : [])
|
|
202
|
+
...(binding.blockers.length ? ['CODEX_LB_CREDENTIAL_BINDING'] : []),
|
|
203
|
+
...(keychainBlockers.length ? ['CODEX_LB_KEYCHAIN'] : [])
|
|
201
204
|
];
|
|
202
205
|
return {
|
|
203
206
|
schema: 'sks.codex-lb-env.v1',
|
|
@@ -214,7 +217,10 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
214
217
|
fingerprint: apiKeySha256 ? apiKeySha256.slice(0, 16) : null
|
|
215
218
|
},
|
|
216
219
|
secret_api_key: apiKeyUsable ? apiKey : null,
|
|
217
|
-
credential_binding:
|
|
220
|
+
credential_binding: {
|
|
221
|
+
...binding,
|
|
222
|
+
blockers: [...binding.blockers, ...keychainBlockers]
|
|
223
|
+
},
|
|
218
224
|
env_paths: envPaths,
|
|
219
225
|
keychain: {
|
|
220
226
|
checked: keychain.checked,
|
|
@@ -223,6 +229,20 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
223
229
|
}
|
|
224
230
|
};
|
|
225
231
|
}
|
|
232
|
+
async function keychainReadIsNeeded(opts, metadata, nonKeychainKeys) {
|
|
233
|
+
if (opts.forceKeychainRead === true)
|
|
234
|
+
return true;
|
|
235
|
+
const candidates = nonKeychainKeys.map((key) => String(key || '').trim()).filter(Boolean);
|
|
236
|
+
if (candidates.length === 0)
|
|
237
|
+
return true;
|
|
238
|
+
if (!metadata.valid || !metadata.apiKeySha256)
|
|
239
|
+
return false;
|
|
240
|
+
for (const candidate of candidates) {
|
|
241
|
+
if ((await sha256Full(candidate)) === metadata.apiKeySha256)
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
226
246
|
async function readCodexLbCredentialMetadata(file) {
|
|
227
247
|
if (!(await exists(file)))
|
|
228
248
|
return { present: false, valid: false, baseUrl: '', apiKeySha256: '' };
|
|
@@ -308,38 +328,32 @@ export async function writeCodexLbKeychain(apiKey, opts = {}) {
|
|
|
308
328
|
return { ok: false, status: 'missing_api_key' };
|
|
309
329
|
if (process.platform !== 'darwin' && !opts.forceMacos)
|
|
310
330
|
return { ok: false, status: 'not_macos' };
|
|
311
|
-
const swift = opts.swiftBin || await which('swift').catch(() => null) || (await exists('/usr/bin/swift') ? '/usr/bin/swift' : null);
|
|
312
|
-
if (!swift)
|
|
313
|
-
return { ok: false, status: 'keychain_writer_unavailable' };
|
|
314
331
|
const account = opts.account || process.env.USER || 'sks';
|
|
315
|
-
const service = opts.service ||
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
maxOutputBytes: 8192
|
|
320
|
-
});
|
|
332
|
+
const service = opts.service || CODEX_LB_SECURE_KEYCHAIN_SERVICE;
|
|
333
|
+
const legacyService = opts.legacyService === false
|
|
334
|
+
? ''
|
|
335
|
+
: opts.legacyService || (opts.service ? '' : CODEX_LB_LEGACY_KEYCHAIN_SERVICE);
|
|
321
336
|
return {
|
|
322
|
-
ok:
|
|
323
|
-
status:
|
|
337
|
+
ok: false,
|
|
338
|
+
status: 'keychain_acl_helper_unavailable',
|
|
324
339
|
account,
|
|
325
340
|
service,
|
|
326
|
-
|
|
341
|
+
legacy_service: legacyService || null,
|
|
342
|
+
keychain_state_verified: true,
|
|
343
|
+
keychain_state_status: 'unchanged',
|
|
344
|
+
timed_out: false,
|
|
345
|
+
error: 'Dedicated signed Keychain helper unavailable; use ~/.codex/sks-codex-lb.env (mode 0600).'
|
|
327
346
|
};
|
|
328
347
|
}
|
|
329
348
|
async function readMacKeychain(opts = {}) {
|
|
330
349
|
if (process.platform !== 'darwin' && !opts.forceMacos)
|
|
331
350
|
return { checked: false, available: false, status: 'not_macos', apiKey: '' };
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
timeoutMs: 5000,
|
|
339
|
-
maxOutputBytes: 8192
|
|
340
|
-
});
|
|
341
|
-
const apiKey = result.code === 0 ? String(result.stdout || '').trim() : '';
|
|
342
|
-
return { checked: true, available: true, status: apiKey ? 'found' : 'missing', apiKey };
|
|
351
|
+
return {
|
|
352
|
+
checked: false,
|
|
353
|
+
available: false,
|
|
354
|
+
status: 'keychain_acl_helper_unavailable',
|
|
355
|
+
apiKey: ''
|
|
356
|
+
};
|
|
343
357
|
}
|
|
344
358
|
async function readEnvFile(file) {
|
|
345
359
|
const text = await readText(file, '');
|
|
@@ -383,6 +397,3 @@ async function sha256Full(value) {
|
|
|
383
397
|
const { createHash } = await import('node:crypto');
|
|
384
398
|
return createHash('sha256').update(value).digest('hex');
|
|
385
399
|
}
|
|
386
|
-
function redactSecret(text, secret) {
|
|
387
|
-
return String(text || '').split(String(secret || '')).join('[redacted]');
|
|
388
|
-
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import fsp from 'node:fs/promises';
|
|
3
4
|
import { ensureDir, exists, readText, writeTextAtomic } from '../fsx.js';
|
|
4
|
-
import { codexLbEnvPath, codexLbMetadataPath, normalizeCodexLbBaseUrl } from './codex-lb-env.js';
|
|
5
|
+
import { CODEX_LB_SECURE_KEYCHAIN_SERVICE, codexLbBaseUrlSecurityBlocker, codexLbEnvPath, codexLbMetadataPath, normalizeCodexLbBaseUrl } from './codex-lb-env.js';
|
|
5
6
|
import { DEFAULT_CODEX_LB_DESKTOP_MODE, DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT } from './desktop-mode.js';
|
|
6
7
|
export function buildCodexLbSetupPlan(answers, opts = {}) {
|
|
7
8
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
@@ -14,8 +15,16 @@ export function buildCodexLbSetupPlan(answers, opts = {}) {
|
|
|
14
15
|
const blockers = [];
|
|
15
16
|
if (!baseUrl)
|
|
16
17
|
blockers.push('missing_host_or_base_url');
|
|
18
|
+
else {
|
|
19
|
+
const transportBlocker = codexLbBaseUrlSecurityBlocker(baseUrl);
|
|
20
|
+
if (transportBlocker)
|
|
21
|
+
blockers.push(transportBlocker);
|
|
22
|
+
}
|
|
17
23
|
if (answers.install_shell_profile !== 'skip' && !answers.write_env_file)
|
|
18
24
|
blockers.push('shell_profile_snippet_requires_env_file');
|
|
25
|
+
if (answers.store_keychain && answers.keychain_helper_verified !== true) {
|
|
26
|
+
blockers.push('keychain_acl_helper_unavailable');
|
|
27
|
+
}
|
|
19
28
|
const actions = [];
|
|
20
29
|
if (desktopMode === 'desktop-native-bridge') {
|
|
21
30
|
actions.push({
|
|
@@ -35,6 +44,7 @@ export function buildCodexLbSetupPlan(answers, opts = {}) {
|
|
|
35
44
|
});
|
|
36
45
|
}
|
|
37
46
|
else if (desktopMode === 'desktop-dual-auth-compat') {
|
|
47
|
+
blockers.push('desktop_dual_auth_compat_requires_global_secret_environment');
|
|
38
48
|
if (gatewayAuthTransport !== 'x-codex-lb-api-key') {
|
|
39
49
|
blockers.push('desktop_compat_requires_x_codex_lb_api_key_transport');
|
|
40
50
|
}
|
|
@@ -60,7 +70,11 @@ export function buildCodexLbSetupPlan(answers, opts = {}) {
|
|
|
60
70
|
actions.push({ type: 'write_env_file', target: envPath, effect: 'write CODEX_LB_BASE_URL and redacted CODEX_LB_API_KEY env loader with chmod 0600' });
|
|
61
71
|
}
|
|
62
72
|
if (answers.store_keychain) {
|
|
63
|
-
actions.push({
|
|
73
|
+
actions.push({
|
|
74
|
+
type: 'store_keychain',
|
|
75
|
+
target: `macOS Keychain service ${CODEX_LB_SECURE_KEYCHAIN_SERVICE}`,
|
|
76
|
+
effect: 'blocked until SKS ships a dedicated signed Keychain helper; reusable interpreters are never trusted for secret access'
|
|
77
|
+
});
|
|
64
78
|
}
|
|
65
79
|
if (answers.sync_launchctl) {
|
|
66
80
|
actions.push({ type: 'sync_launchctl', target: 'macOS launchctl user environment', effect: 'sync non-secret CODEX_LB_BASE_URL only and remove API-key launchd env', command: 'launchctl setenv CODEX_LB_BASE_URL ...; launchctl unsetenv CODEX_LB_API_KEY OPENROUTER_API_KEY' });
|
|
@@ -113,15 +127,78 @@ export async function installCodexLbShellProfileSnippet(opts) {
|
|
|
113
127
|
if (!(await exists(opts.envPath))) {
|
|
114
128
|
return { ok: true, status: 'skipped', skipped: true, reason: 'env_file_not_written', files: [] };
|
|
115
129
|
}
|
|
130
|
+
if (Boolean(opts.expectedFiles) !== Boolean(opts.writeFileIfUnchanged)) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
status: 'setup_cas_contract_incomplete',
|
|
134
|
+
files: [],
|
|
135
|
+
error: 'expectedFiles and writeFileIfUnchanged must be provided together'
|
|
136
|
+
};
|
|
137
|
+
}
|
|
116
138
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
const targets = profileTargets(home, opts.shellProfile);
|
|
140
|
+
const files = [];
|
|
141
|
+
const recoveryPaths = [];
|
|
142
|
+
for (const file of targets) {
|
|
143
|
+
const expected = opts.expectedFiles?.find((entry) => path.resolve(entry.path) === path.resolve(file));
|
|
144
|
+
if (opts.expectedFiles && !expected) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
status: 'setup_snapshot_missing',
|
|
148
|
+
files,
|
|
149
|
+
recovery_paths: recoveryPaths,
|
|
150
|
+
error: `setup_snapshot_missing:${file}`
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (expected?.existed === true && expected.kind !== 'regular') {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
status: 'unsafe_setup_write_target',
|
|
157
|
+
files,
|
|
158
|
+
recovery_paths: recoveryPaths,
|
|
159
|
+
error: `unsafe_setup_write_target:${file}:${expected.kind}`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const current = expected
|
|
163
|
+
? (expected.existed === true
|
|
164
|
+
? Buffer.from(String(expected.bytes_base64 || ''), 'base64').toString('utf8')
|
|
165
|
+
: '')
|
|
166
|
+
: await readText(file, '');
|
|
120
167
|
const block = shellProfileBlock(file, opts.envPath);
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
const text = upsertManagedBlock(current, block);
|
|
169
|
+
const existingMode = expected
|
|
170
|
+
? (expected.existed === true ? Number(expected.mode) & 0o777 : null)
|
|
171
|
+
: await fsp.lstat(file)
|
|
172
|
+
.then((stat) => stat.isFile() && !stat.isSymbolicLink() ? stat.mode & 0o777 : null)
|
|
173
|
+
.catch(() => null);
|
|
174
|
+
const mode = existingMode ?? (0o666 & ~process.umask());
|
|
175
|
+
if (expected && opts.writeFileIfUnchanged) {
|
|
176
|
+
const result = await opts.writeFileIfUnchanged({ file, expected, text, mode });
|
|
177
|
+
if (result.recovery_path)
|
|
178
|
+
recoveryPaths.push(result.recovery_path);
|
|
179
|
+
if (!result.ok) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
status: result.status,
|
|
183
|
+
files,
|
|
184
|
+
recovery_paths: recoveryPaths,
|
|
185
|
+
error: result.error || `${result.status}:${file}`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
await ensureDir(path.dirname(file));
|
|
191
|
+
await writeTextAtomic(file, text);
|
|
192
|
+
}
|
|
193
|
+
await opts.onFileWritten?.({ file, text, mode });
|
|
194
|
+
files.push(file);
|
|
123
195
|
}
|
|
124
|
-
return {
|
|
196
|
+
return {
|
|
197
|
+
ok: true,
|
|
198
|
+
status: 'installed',
|
|
199
|
+
files,
|
|
200
|
+
...(recoveryPaths.length ? { recovery_paths: recoveryPaths } : {})
|
|
201
|
+
};
|
|
125
202
|
}
|
|
126
203
|
export function selectedCodexLbPersistenceModes(answers) {
|
|
127
204
|
const modes = [];
|