sneakoscope 1.0.4 → 1.0.5

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 (42) hide show
  1. package/README.md +28 -1
  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/build-manifest.json +7 -1
  7. package/dist/cli/install-helpers.d.ts +57 -0
  8. package/dist/cli/install-helpers.js +42 -5
  9. package/dist/commands/codex-lb.js +11 -5
  10. package/dist/commands/wiki.d.ts +1 -1
  11. package/dist/core/codex-compat/codex-compat-report.d.ts +32 -0
  12. package/dist/core/codex-compat/codex-compat-report.js +15 -3
  13. package/dist/core/codex-compat/codex-hook-output-builders.d.ts +45 -0
  14. package/dist/core/codex-compat/codex-hook-output-builders.js +122 -0
  15. package/dist/core/codex-compat/codex-hook-output-normalizer.d.ts +1 -1
  16. package/dist/core/codex-compat/codex-hook-output-normalizer.js +25 -48
  17. package/dist/core/codex-compat/codex-hook-schema.d.ts +6 -1
  18. package/dist/core/codex-compat/codex-hook-schema.js +14 -1
  19. package/dist/core/codex-compat/codex-hook-semantic-validator.d.ts +19 -0
  20. package/dist/core/codex-compat/codex-hook-semantic-validator.js +208 -0
  21. package/dist/core/codex-compat/codex-hook-warning-detector.d.ts +1 -0
  22. package/dist/core/codex-compat/codex-hook-warning-detector.js +9 -1
  23. package/dist/core/codex-compat/codex-schema-snapshot.d.ts +1 -0
  24. package/dist/core/codex-compat/codex-schema-snapshot.js +10 -2
  25. package/dist/core/codex-lb/codex-lb-env.d.ts +42 -0
  26. package/dist/core/codex-lb/codex-lb-env.js +150 -0
  27. package/dist/core/commands/wiki-command.d.ts +2 -2
  28. package/dist/core/fsx.d.ts +1 -1
  29. package/dist/core/fsx.js +1 -1
  30. package/dist/core/hooks-runtime.js +17 -48
  31. package/dist/core/proof/evidence-collector.d.ts +1 -1
  32. package/dist/core/proof/route-finalizer.js +15 -0
  33. package/dist/core/triwiki-wrongness/wrongness-cli.d.ts +2 -2
  34. package/dist/core/triwiki-wrongness/wrongness-ledger.js +6 -5
  35. package/dist/core/triwiki-wrongness/wrongness-proof-linker.d.ts +1 -1
  36. package/dist/core/triwiki-wrongness/wrongness-retrieval.d.ts +1 -1
  37. package/dist/core/triwiki-wrongness/wrongness-schema.d.ts +1 -1
  38. package/dist/core/triwiki-wrongness/wrongness-schema.js +12 -1
  39. package/dist/core/version.d.ts +1 -1
  40. package/dist/core/version.js +1 -1
  41. package/dist/vendor/openai-codex/rust-v0.131.0/hooks/snapshot-metadata.json +2 -0
  42. package/package.json +5 -2
package/README.md CHANGED
@@ -4,7 +4,9 @@ Fast legacy-free proof-first Codex trust layer with image-based Voxel TriWiki.
4
4
 
5
5
  Sneakoscope Codex (`sks`) is a Codex CLI/App harness that makes repeatable Codex work auditable.
6
6
 
7
- SKS **1.0.4** targets OpenAI Codex CLI `rust-v0.131.0`: hook outputs are validated against vendored upstream schemas, `sks codex-lb setup` is a guided/redacted setup path, and macOS Computer Use is treated as a Codex App visual evidence capability independent from MAD-SKS.
7
+ SKS **1.0.5** seals the Codex trust harness: hook outputs are validated against both vendored OpenAI Codex CLI `rust-v0.131.0` schemas and runtime semantic parser rules, codex-lb setup survives macOS user-session launches through env-file/Keychain/launchctl-aware repair surfaces, and Computer Use is the preferred macOS visual evidence capability when available.
8
+
9
+ SKS **1.0.4** introduced the `rust-v0.131.0` schema snapshot, guided codex-lb setup path, and Computer Use/MAD-SKS separation that 1.0.5 now hardens into release gates.
8
10
 
9
11
  SKS **1.0.3** adds git-collaboration hygiene for shared TriWiki memory: `sks git ...`, tracked shared shards under `.sneakoscope/wiki/**`, runtime-only ignores, shared wrongness publish/sync, and Codex App hook trust-state generation for current hook trust syntax.
10
12
 
@@ -19,6 +21,31 @@ SKS does not try to clone every other harness. It focuses on one thing: making C
19
21
  ![Sneakoscope Codex architecture and pipeline](https://raw.githubusercontent.com/mandarange/Sneakoscope-Codex/dev/docs/assets/sneakoscope-architecture-pipeline.jpg)
20
22
 
21
23
 
24
+ ## 1.0.5 Ultimate Harness Seal
25
+
26
+ SKS 1.0.5 treats Codex hook semantic compatibility as stricter than schema compatibility. `sks hooks warning-check --json` and `npm run hooks:semantic-check` fail if an output uses `permissionDecision:"ask"`, PreToolUse `allow` without `updatedInput`, Stop `continue:false`, `stopReason`, `suppressOutput`, snake_case keys, unknown fields, or legacy top-level hook decisions.
27
+
28
+ codex-lb setup now has a durable setup/repair path:
29
+
30
+ ```bash
31
+ sks codex-lb setup --host lb.example.com --api-key-stdin --yes --json
32
+ sks codex-lb status --json
33
+ sks codex-lb doctor --deep --json
34
+ npm run codex-lb:missing-env-regression
35
+ ```
36
+
37
+ The API key is written only to redacted status surfaces. The env file is `~/.codex/sks-codex-lb.env`, metadata is `~/.codex/sks-codex-lb.json`, and reports expose only a redacted presence state plus a fingerprint. Raw `CODEX_LB_API_KEY` missing-env errors are release failures.
38
+
39
+ Computer Use is a Codex App/macOS capability, not a MAD-SKS or DB permission. Visual routes use:
40
+
41
+ ```bash
42
+ sks computer-use status --json
43
+ sks computer-use require --route '$QA-LOOP' --json
44
+ npm run computer-use:visual-route-fixture
45
+ ```
46
+
47
+ If Codex App or macOS blocks the capability, SKS records `external_capability_blocked`, `codex_app_missing`, `macos_permission_missing`, or the closest structured status and does not fabricate UI evidence.
48
+
22
49
  ## 1.0.4 Codex CLI Compatibility
23
50
 
24
51
  SKS 1.0.4 targets OpenAI Codex CLI `rust-v0.131.0`. Hook outputs are validated against vendored upstream schemas, so SKS fails release checks if it emits deprecated hook shapes or unknown fields. `sks codex-lb setup` now guides users through domain/base URL and API key setup, stores secrets securely, and prevents raw `CODEX_LB_API_KEY` missing messages. On macOS, Computer Use is treated as a first-class Codex App visual evidence capability and is never blocked by MAD-SKS or a generic SKS safety policy.
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "1.0.4"
79
+ version = "1.0.5"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "1.0.4"
3
+ version = "1.0.5"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -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 1.0.4"),
7
+ Some("--version") => println!("sks-rs 1.0.5"),
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '1.0.4';
2
+ const FAST_PACKAGE_VERSION = '1.0.5';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--version' || args[0] === '-v' || args[0] === 'version') {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "typescript": true,
5
5
  "mjs_runtime_files": 0,
6
6
  "files": [
@@ -188,10 +188,14 @@
188
188
  "core/codex-compat/codex-compat-report.js",
189
189
  "core/codex-compat/codex-config-policy.d.ts",
190
190
  "core/codex-compat/codex-config-policy.js",
191
+ "core/codex-compat/codex-hook-output-builders.d.ts",
192
+ "core/codex-compat/codex-hook-output-builders.js",
191
193
  "core/codex-compat/codex-hook-output-normalizer.d.ts",
192
194
  "core/codex-compat/codex-hook-output-normalizer.js",
193
195
  "core/codex-compat/codex-hook-schema.d.ts",
194
196
  "core/codex-compat/codex-hook-schema.js",
197
+ "core/codex-compat/codex-hook-semantic-validator.d.ts",
198
+ "core/codex-compat/codex-hook-semantic-validator.js",
195
199
  "core/codex-compat/codex-hook-warning-detector.d.ts",
196
200
  "core/codex-compat/codex-hook-warning-detector.js",
197
201
  "core/codex-compat/codex-schema-snapshot.d.ts",
@@ -202,6 +206,8 @@
202
206
  "core/codex-compat/codex-version.js",
203
207
  "core/codex-lb-circuit.d.ts",
204
208
  "core/codex-lb-circuit.js",
209
+ "core/codex-lb/codex-lb-env.d.ts",
210
+ "core/codex-lb/codex-lb-env.js",
205
211
  "core/codex-model-guard.d.ts",
206
212
  "core/codex-model-guard.js",
207
213
  "core/commands/autoresearch-command.d.ts",
@@ -58,8 +58,11 @@ export type ConfigureCodexLbResult = {
58
58
  status: string;
59
59
  config_path?: string;
60
60
  env_path?: string;
61
+ metadata_path?: string;
61
62
  base_url?: string;
62
63
  env_key?: string;
64
+ keychain?: Record<string, unknown>;
65
+ warnings?: string[];
63
66
  auth_reconcile?: CodexLbAuthReconcileResult;
64
67
  codex_lb?: CodexLbStatusSnapshot;
65
68
  codex_environment?: CodexLbEnvSyncResult;
@@ -97,6 +100,24 @@ export declare function codexLbStatus(opts?: any): Promise<{
97
100
  env_file: boolean;
98
101
  env_key_configured: boolean;
99
102
  env_base_url_configured: boolean;
103
+ env_loader: {
104
+ configured: boolean;
105
+ missing: string[];
106
+ source: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource;
107
+ source_priority: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource[];
108
+ api_key: {
109
+ present: boolean;
110
+ source: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource | null;
111
+ redacted: true;
112
+ fingerprint: string | null;
113
+ };
114
+ keychain: {
115
+ checked: boolean;
116
+ available: boolean;
117
+ status: string;
118
+ };
119
+ env_paths: string[];
120
+ };
100
121
  base_url: string | null;
101
122
  auth_path: any;
102
123
  auth_mode: string;
@@ -185,6 +206,24 @@ export declare function maybePromptCodexLbSetupForLaunch(args?: any, opts?: any)
185
206
  env_file: boolean;
186
207
  env_key_configured: boolean;
187
208
  env_base_url_configured: boolean;
209
+ env_loader: {
210
+ configured: boolean;
211
+ missing: string[];
212
+ source: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource;
213
+ source_priority: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource[];
214
+ api_key: {
215
+ present: boolean;
216
+ source: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource | null;
217
+ redacted: true;
218
+ fingerprint: string | null;
219
+ };
220
+ keychain: {
221
+ checked: boolean;
222
+ available: boolean;
223
+ status: string;
224
+ };
225
+ env_paths: string[];
226
+ };
188
227
  base_url: string | null;
189
228
  auth_path: any;
190
229
  auth_mode: string;
@@ -205,6 +244,24 @@ export declare function maybePromptCodexLbSetupForLaunch(args?: any, opts?: any)
205
244
  env_file: boolean;
206
245
  env_key_configured: boolean;
207
246
  env_base_url_configured: boolean;
247
+ env_loader: {
248
+ configured: boolean;
249
+ missing: string[];
250
+ source: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource;
251
+ source_priority: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource[];
252
+ api_key: {
253
+ present: boolean;
254
+ source: import("../core/codex-lb/codex-lb-env.js").CodexLbEnvSource | null;
255
+ redacted: true;
256
+ fingerprint: string | null;
257
+ };
258
+ keychain: {
259
+ checked: boolean;
260
+ available: boolean;
261
+ status: string;
262
+ };
263
+ env_paths: string[];
264
+ };
208
265
  base_url: string | null;
209
266
  auth_path: any;
210
267
  auth_mode: string;
@@ -11,6 +11,7 @@ import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext
11
11
  import { codexLaunchCommand, platformTmuxInstallHint, tmuxReadiness, tmuxReadinessCatchFallback } from '../core/tmux-ui.js';
12
12
  import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
13
13
  import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
14
+ import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
14
15
  const DEFAULT_CODEX_APP_PLUGINS = [
15
16
  ['browser', 'openai-bundled'],
16
17
  ['chrome', 'openai-bundled'],
@@ -293,7 +294,7 @@ async function restorePostinstallCodexLbConfigSnapshot(snapshot) {
293
294
  export function normalizeCodexLbBaseUrl(input = '') {
294
295
  let host = String(input || '').trim();
295
296
  if (!host)
296
- host = 'http://127.0.0.1:2455';
297
+ return '';
297
298
  if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(host))
298
299
  host = `https://${host}`;
299
300
  host = host.replace(/\/+$/, '');
@@ -303,16 +304,35 @@ export async function configureCodexLb(opts = {}) {
303
304
  const home = opts.home || process.env.HOME || os.homedir();
304
305
  const configPath = opts.configPath || codexLbConfigPath(home);
305
306
  const envPath = opts.envPath || codexLbEnvPath(home);
306
- const baseUrl = normalizeCodexLbBaseUrl(opts.host || opts.baseUrl);
307
+ const rawHost = String(opts.host || opts.baseUrl || '');
308
+ const baseUrl = normalizeCodexLbBaseUrl(rawHost);
307
309
  const apiKey = String(opts.apiKey || '').trim();
310
+ if (!baseUrl)
311
+ return { ok: false, status: 'missing_host_or_base_url', config_path: configPath, env_path: envPath };
312
+ if (/[\u0000-\u001f\u007f\s]/.test(rawHost.trim()))
313
+ return { ok: false, status: 'invalid_host_or_base_url', config_path: configPath, env_path: envPath, error: 'host_or_base_url_contains_whitespace_or_control_character' };
308
314
  if (!apiKey)
309
315
  return { ok: false, status: 'missing_api_key', config_path: configPath, env_path: envPath };
316
+ const insecureLocalWarning = /^http:\/\//i.test(baseUrl) && !/^http:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/i.test(baseUrl) && !opts.allowInsecureHttp
317
+ ? ['codex-lb base URL uses http outside localhost; prefer https or pass an explicit allow flag in the calling surface.']
318
+ : [];
310
319
  await ensureDir(path.dirname(configPath));
311
320
  const current = await readText(configPath, '');
312
321
  const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, baseUrl));
313
322
  await writeTextAtomic(configPath, next);
314
323
  await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
315
324
  await fsp.chmod(envPath, 0o600).catch(() => { });
325
+ const keyFingerprint = await sha256Text(apiKey);
326
+ const metadataPath = opts.metadataPath || codexLbMetadataPath(home);
327
+ await writeTextAtomic(metadataPath, `${JSON.stringify({
328
+ schema: 'sks.codex-lb-metadata.v1',
329
+ base_url: baseUrl,
330
+ updated_at: new Date().toISOString(),
331
+ source: opts.source || 'setup',
332
+ api_key: { redacted: true, sha256: keyFingerprint }
333
+ }, null, 2)}\n`);
334
+ await fsp.chmod(metadataPath, 0o600).catch(() => { });
335
+ const keychain = opts.keychain ? await writeCodexLbKeychain(apiKey, opts).catch((err) => ({ ok: false, status: 'keychain_store_failed', error: err.message })) : { ok: false, status: 'skipped' };
316
336
  const codexEnvironment = await syncCodexLbProviderEnvironment({ env_path: envPath, base_url: baseUrl }, { ...opts, home });
317
337
  const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home, force: true });
318
338
  const codexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
@@ -324,8 +344,11 @@ export async function configureCodexLb(opts = {}) {
324
344
  status: ok ? 'configured' : (codexEnvironment.status || codexLogin.status),
325
345
  config_path: configPath,
326
346
  env_path: envPath,
347
+ metadata_path: metadataPath,
327
348
  base_url: baseUrl,
328
349
  env_key: 'CODEX_LB_API_KEY',
350
+ keychain,
351
+ warnings: insecureLocalWarning,
329
352
  auth_reconcile: authReconcile,
330
353
  codex_lb: finalCodexLb,
331
354
  codex_environment: codexEnvironment,
@@ -340,13 +363,14 @@ export async function codexLbStatus(opts = {}) {
340
363
  const config = await readText(configPath, '');
341
364
  const envExists = await exists(envPath);
342
365
  const envText = envExists ? await readText(envPath, '') : '';
366
+ const envLoad = await loadCodexLbEnv({ ...opts, home, envPath });
343
367
  const authPath = opts.authPath || codexAuthPath(home);
344
368
  const authText = await readText(authPath, '');
345
369
  const authMode = codexAuthModeSummary(authText);
346
- const envKeyConfigured = Boolean(parseCodexLbEnvKey(envText));
370
+ const envKeyConfigured = Boolean(envLoad.api_key.present);
347
371
  const providerConfigured = /\[model_providers\.codex-lb\]/.test(config);
348
372
  const selected = hasTopLevelCodexLbSelected(config);
349
- const baseUrl = codexLbProviderBaseUrl(config) || parseCodexLbEnvBaseUrl(envText) || null;
373
+ const baseUrl = codexLbProviderBaseUrl(config) || envLoad.base_url || null;
350
374
  const providerRequiresOpenAiAuth = codexLbProviderRequiresOpenAiAuth(config);
351
375
  return {
352
376
  ok: providerConfigured && envKeyConfigured && Boolean(baseUrl) && providerRequiresOpenAiAuth,
@@ -357,7 +381,16 @@ export async function codexLbStatus(opts = {}) {
357
381
  selected,
358
382
  env_file: envExists,
359
383
  env_key_configured: envKeyConfigured,
360
- env_base_url_configured: Boolean(parseCodexLbEnvBaseUrl(envText)),
384
+ env_base_url_configured: Boolean(envLoad.base_url),
385
+ env_loader: {
386
+ configured: envLoad.configured,
387
+ missing: envLoad.missing,
388
+ source: envLoad.source,
389
+ source_priority: envLoad.source_priority,
390
+ api_key: envLoad.api_key,
391
+ keychain: envLoad.keychain,
392
+ env_paths: envLoad.env_paths
393
+ },
361
394
  base_url: baseUrl,
362
395
  auth_path: authPath,
363
396
  auth_mode: authMode.mode,
@@ -1482,6 +1515,10 @@ function redactSecretText(text = '', secrets = []) {
1482
1515
  }
1483
1516
  return out;
1484
1517
  }
1518
+ async function sha256Text(value = '') {
1519
+ const { createHash } = await import('node:crypto');
1520
+ return createHash('sha256').update(String(value || '')).digest('hex');
1521
+ }
1485
1522
  function escapeRegExp(value) {
1486
1523
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1487
1524
  }
@@ -39,7 +39,8 @@ export async function run(command, args = []) {
39
39
  }
40
40
  if (action === 'health' || action === 'verify-chain' || action === 'chain') {
41
41
  const status = await codexLbStatus();
42
- const result = status.ok ? await checkCodexLbResponseChain(status, { force: true, root }) : { ok: false, status: 'not_configured', codex_lb: status };
42
+ const blocker = !status.env_key_configured ? 'missing_env_key' : !status.base_url ? 'missing_base_url' : 'not_configured';
43
+ const result = status.ok ? await checkCodexLbResponseChain(status, { force: true, root }) : { ok: false, status: blocker, codex_lb: status };
43
44
  if (flag(args, '--json'))
44
45
  return printJson(result);
45
46
  console.log(`codex-lb response chain: ${result.ok ? 'ok' : `failed (${result.status})`}`);
@@ -97,7 +98,7 @@ export async function run(command, args = []) {
97
98
  process.exitCode = 1;
98
99
  return;
99
100
  }
100
- const result = await configureCodexLb({ host: options.host, apiKey: options.apiKey });
101
+ const result = await configureCodexLb({ host: options.host, apiKey: options.apiKey, keychain: options.keychain });
101
102
  const shaped = { schema: 'sks.codex-lb-setup.v1', ...result, api_key: { present: Boolean(options.apiKey), redacted: true }, env_file_chmod: '0600' };
102
103
  if (options.health)
103
104
  shaped.chain_health = result.ok ? await checkCodexLbResponseChain(result, { force: true, root }) : null;
@@ -147,11 +148,13 @@ function shapeCodexLbStatus(status = {}) {
147
148
  ...status,
148
149
  configured: Boolean(status.ok),
149
150
  setup_needed: !status.ok,
151
+ repair_available: !status.ok,
150
152
  api_key: {
151
153
  present: Boolean(status.env_key_configured),
152
- source: status.env_key_configured ? 'env-file' : null,
154
+ source: status.env_loader?.api_key?.source || null,
153
155
  redacted: true
154
156
  },
157
+ env_loader: status.env_loader || null,
155
158
  env_auto_load: Boolean(status.env_file && status.env_key_configured),
156
159
  guidance: status.ok ? [] : [
157
160
  'codex-lb API key is not configured.',
@@ -164,6 +167,7 @@ async function codexLbSetupOptions(args = []) {
164
167
  const baseUrl = readOption(args, '--base-url', null);
165
168
  let host = baseUrl || readOption(args, '--host', readOption(args, '--domain', null));
166
169
  let apiKey = readOption(args, '--api-key', readOption(args, '--key', null));
170
+ let keychain = flag(args, '--keychain');
167
171
  if (flag(args, '--api-key-stdin'))
168
172
  apiKey = (await readStdin()).trim();
169
173
  let health = flag(args, '--health') || flag(args, '--check');
@@ -173,10 +177,12 @@ async function codexLbSetupOptions(args = []) {
173
177
  apiKey ||= (await askHidden('2. API key?\n Input hidden. Value will be stored securely and never printed.\n> ')).trim();
174
178
  await ask('3. Use this codex-lb as default for Codex launches? [Y/n] ');
175
179
  await ask('4. Write shell env loader to ~/.codex/sks-codex-lb.env? [Y/n] ');
176
- const runHealth = (await ask('5. Run health check now? [Y/n] ')).trim();
180
+ const storeKeychain = (await ask('5. Store the key in macOS Keychain when available? [Y/n] ')).trim();
181
+ keychain = !/^(n|no|아니|아니요|ㄴ)$/i.test(storeKeychain || 'y');
182
+ const runHealth = (await ask('6. Run health check now? [Y/n] ')).trim();
177
183
  health = !/^(n|no|아니|아니요|ㄴ)$/i.test(runHealth || 'y');
178
184
  }
179
- return { host, apiKey, health };
185
+ return { host, apiKey, health, keychain };
180
186
  }
181
187
  function canAskInteractive(args = []) {
182
188
  return !flag(args, '--json') && !flag(args, '--yes') && Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true');
@@ -15,7 +15,7 @@ export declare function run(_command: any, args?: any): Promise<void | {
15
15
  };
16
16
  active_records: {
17
17
  id: string;
18
- kind: "incorrect_claim" | "overconfident_claim" | "stale_evidence" | "missing_evidence" | "test_failure" | "route_misclassification" | "scout_error" | "visual_anchor_error" | "image_bbox_error" | "db_safety_false_positive" | "db_safety_false_negative" | "hook_policy_mismatch" | "codex_lb_health_misread" | "mock_real_confusion" | "user_intent_misread" | "artifact_schema_error" | "trust_status_overclaim";
18
+ kind: "incorrect_claim" | "overconfident_claim" | "stale_evidence" | "missing_evidence" | "test_failure" | "route_misclassification" | "scout_error" | "visual_anchor_error" | "image_bbox_error" | "db_safety_false_positive" | "db_safety_false_negative" | "hook_policy_mismatch" | "hook_semantic_mismatch" | "codex_lb_health_misread" | "codex_lb_missing_env_raw_message" | "computer_use_policy_misclassification" | "mock_real_confusion" | "user_intent_misread" | "artifact_schema_error" | "trust_status_overclaim";
19
19
  severity: "high" | "low" | "medium" | "critical";
20
20
  route: string | null;
21
21
  claim: string;
@@ -6,6 +6,22 @@ export declare function codexCompatibilityReport(opts?: any): Promise<{
6
6
  snapshot: string;
7
7
  ok: boolean;
8
8
  files: number;
9
+ metadata: {
10
+ upstream: any;
11
+ tag: any;
12
+ commit: any;
13
+ captured_at: any;
14
+ };
15
+ };
16
+ hooks_semantic: {
17
+ ok: boolean;
18
+ warnings_count: number;
19
+ events: {
20
+ event: "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "PermissionRequest" | "Stop" | "PreCompact" | "PostCompact" | "SessionStart";
21
+ checked: number;
22
+ ok: boolean;
23
+ warnings: string[];
24
+ }[];
9
25
  };
10
26
  ok: boolean;
11
27
  status: string;
@@ -23,6 +39,22 @@ export declare function codexDoctorReport(opts?: any): Promise<{
23
39
  snapshot: string;
24
40
  ok: boolean;
25
41
  files: number;
42
+ metadata: {
43
+ upstream: any;
44
+ tag: any;
45
+ commit: any;
46
+ captured_at: any;
47
+ };
48
+ };
49
+ hooks_semantic: {
50
+ ok: boolean;
51
+ warnings_count: number;
52
+ events: {
53
+ event: "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "PermissionRequest" | "Stop" | "PreCompact" | "PostCompact" | "SessionStart";
54
+ checked: number;
55
+ ok: boolean;
56
+ warnings: string[];
57
+ }[];
26
58
  };
27
59
  ok: boolean;
28
60
  status: string;
@@ -7,7 +7,8 @@ export async function codexCompatibilityReport(opts = {}) {
7
7
  const root = opts.root || await projectRoot();
8
8
  const version = await codexVersionReport(opts);
9
9
  const snapshot = await codexSchemaSnapshotReport();
10
- const ok = Boolean(version.policy.ok && snapshot.ok);
10
+ const hooks = await codexHookWarningCheck(root, { recordWrongness: false });
11
+ const ok = Boolean(version.policy.ok && snapshot.ok && hooks.ok);
11
12
  return {
12
13
  schema: CODEX_COMPAT_SCHEMA,
13
14
  required_baseline: CODEX_REQUIRED_BASELINE_TAG,
@@ -15,11 +16,22 @@ export async function codexCompatibilityReport(opts = {}) {
15
16
  hooks_schema: {
16
17
  snapshot: CODEX_REQUIRED_BASELINE_TAG,
17
18
  ok: snapshot.ok,
18
- files: snapshot.files.length
19
+ files: snapshot.files.length,
20
+ metadata: {
21
+ upstream: snapshot.metadata?.upstream || null,
22
+ tag: snapshot.metadata?.tag || null,
23
+ commit: snapshot.metadata?.commit || null,
24
+ captured_at: snapshot.metadata?.captured_at || null
25
+ }
26
+ },
27
+ hooks_semantic: {
28
+ ok: hooks.ok,
29
+ warnings_count: hooks.warnings_count,
30
+ events: hooks.events
19
31
  },
20
32
  ok,
21
33
  status: ok ? version.policy.status : 'blocked',
22
- warnings: version.policy.warnings,
34
+ warnings: [...version.policy.warnings, ...(hooks.ok ? [] : hooks.warnings)],
23
35
  root
24
36
  };
25
37
  }
@@ -0,0 +1,45 @@
1
+ import { type CodexHookEventName } from './codex-schema-snapshot.js';
2
+ export type CodexHookOutput = Record<string, unknown>;
3
+ export declare function buildPreToolUseContinue(options?: {
4
+ systemMessage?: string;
5
+ }): CodexHookOutput;
6
+ export declare function buildPreToolUseDeny(reason: unknown, options?: {
7
+ systemMessage?: string;
8
+ }): CodexHookOutput;
9
+ export declare function buildPreToolUseAllowRewrite(updatedInput: unknown, options?: {
10
+ systemMessage?: string;
11
+ }): CodexHookOutput;
12
+ export declare function buildPermissionRequestAllow(options?: {
13
+ systemMessage?: string;
14
+ }): CodexHookOutput;
15
+ export declare function buildPermissionRequestDeny(message: unknown, options?: {
16
+ systemMessage?: string;
17
+ }): CodexHookOutput;
18
+ export declare function buildPostToolUseContinue(options?: {
19
+ additionalContext?: string;
20
+ systemMessage?: string;
21
+ }): CodexHookOutput;
22
+ export declare function buildPostToolUseBlock(reason: unknown, options?: {
23
+ systemMessage?: string;
24
+ }): CodexHookOutput;
25
+ export declare function buildUserPromptSubmitContinue(options?: {
26
+ additionalContext?: string;
27
+ systemMessage?: string;
28
+ }): CodexHookOutput;
29
+ export declare function buildUserPromptSubmitBlock(reason: unknown, options?: {
30
+ systemMessage?: string;
31
+ }): CodexHookOutput;
32
+ export declare function buildStopContinue(options?: {
33
+ systemMessage?: string;
34
+ }): CodexHookOutput;
35
+ export declare function buildStopBlock(reason: unknown, options?: {
36
+ systemMessage?: string;
37
+ }): CodexHookOutput;
38
+ export declare function buildCompactContinue(event?: Extract<CodexHookEventName, 'PreCompact' | 'PostCompact'>, options?: {
39
+ systemMessage?: string;
40
+ }): CodexHookOutput;
41
+ export declare function buildSessionStartContinue(options?: {
42
+ additionalContext?: string;
43
+ systemMessage?: string;
44
+ }): CodexHookOutput;
45
+ //# sourceMappingURL=codex-hook-output-builders.d.ts.map
@@ -0,0 +1,122 @@
1
+ import {} from './codex-schema-snapshot.js';
2
+ export function buildPreToolUseContinue(options = {}) {
3
+ return withOptionalSystemMessage({ continue: true }, options.systemMessage);
4
+ }
5
+ export function buildPreToolUseDeny(reason, options = {}) {
6
+ const trimmed = requiredReason(reason, 'PreToolUse deny requires a non-empty reason');
7
+ return withOptionalSystemMessage({
8
+ continue: true,
9
+ hookSpecificOutput: {
10
+ hookEventName: 'PreToolUse',
11
+ permissionDecision: 'deny',
12
+ permissionDecisionReason: trimmed
13
+ }
14
+ }, options.systemMessage);
15
+ }
16
+ export function buildPreToolUseAllowRewrite(updatedInput, options = {}) {
17
+ if (updatedInput === undefined || updatedInput === null) {
18
+ throw new Error('PreToolUse allow rewrite requires updatedInput');
19
+ }
20
+ return withOptionalSystemMessage({
21
+ continue: true,
22
+ hookSpecificOutput: {
23
+ hookEventName: 'PreToolUse',
24
+ permissionDecision: 'allow',
25
+ updatedInput
26
+ }
27
+ }, options.systemMessage);
28
+ }
29
+ export function buildPermissionRequestAllow(options = {}) {
30
+ return withOptionalSystemMessage({
31
+ continue: true,
32
+ hookSpecificOutput: {
33
+ hookEventName: 'PermissionRequest',
34
+ decision: { behavior: 'allow' }
35
+ }
36
+ }, options.systemMessage);
37
+ }
38
+ export function buildPermissionRequestDeny(message, options = {}) {
39
+ const trimmed = requiredReason(message, 'PermissionRequest deny requires a non-empty message');
40
+ return withOptionalSystemMessage({
41
+ continue: true,
42
+ hookSpecificOutput: {
43
+ hookEventName: 'PermissionRequest',
44
+ decision: { behavior: 'deny', message: trimmed }
45
+ }
46
+ }, options.systemMessage);
47
+ }
48
+ export function buildPostToolUseContinue(options = {}) {
49
+ const output = { continue: true };
50
+ const additionalContext = optionalText(options.additionalContext);
51
+ if (additionalContext) {
52
+ output.hookSpecificOutput = {
53
+ hookEventName: 'PostToolUse',
54
+ additionalContext
55
+ };
56
+ }
57
+ return withOptionalSystemMessage(output, options.systemMessage);
58
+ }
59
+ export function buildPostToolUseBlock(reason, options = {}) {
60
+ return withOptionalSystemMessage({
61
+ continue: true,
62
+ decision: 'block',
63
+ reason: requiredReason(reason, 'PostToolUse block requires a non-empty reason')
64
+ }, options.systemMessage);
65
+ }
66
+ export function buildUserPromptSubmitContinue(options = {}) {
67
+ const output = { continue: true };
68
+ const additionalContext = optionalText(options.additionalContext);
69
+ if (additionalContext) {
70
+ output.hookSpecificOutput = {
71
+ hookEventName: 'UserPromptSubmit',
72
+ additionalContext
73
+ };
74
+ }
75
+ return withOptionalSystemMessage(output, options.systemMessage);
76
+ }
77
+ export function buildUserPromptSubmitBlock(reason, options = {}) {
78
+ return withOptionalSystemMessage({
79
+ continue: true,
80
+ decision: 'block',
81
+ reason: requiredReason(reason, 'UserPromptSubmit block requires a non-empty reason')
82
+ }, options.systemMessage);
83
+ }
84
+ export function buildStopContinue(options = {}) {
85
+ return withOptionalSystemMessage({ continue: true }, options.systemMessage);
86
+ }
87
+ export function buildStopBlock(reason, options = {}) {
88
+ return withOptionalSystemMessage({
89
+ continue: true,
90
+ decision: 'block',
91
+ reason: requiredReason(reason, 'Stop block requires a non-empty reason')
92
+ }, options.systemMessage);
93
+ }
94
+ export function buildCompactContinue(event = 'PreCompact', options = {}) {
95
+ void event;
96
+ return withOptionalSystemMessage({ continue: true }, options.systemMessage);
97
+ }
98
+ export function buildSessionStartContinue(options = {}) {
99
+ const output = { continue: true };
100
+ const additionalContext = optionalText(options.additionalContext);
101
+ if (additionalContext) {
102
+ output.hookSpecificOutput = {
103
+ hookEventName: 'SessionStart',
104
+ additionalContext
105
+ };
106
+ }
107
+ return withOptionalSystemMessage(output, options.systemMessage);
108
+ }
109
+ function withOptionalSystemMessage(output, systemMessage) {
110
+ const message = optionalText(systemMessage);
111
+ return message ? { ...output, systemMessage: message } : output;
112
+ }
113
+ function requiredReason(value, fallback) {
114
+ const trimmed = optionalText(value);
115
+ if (!trimmed)
116
+ throw new Error(fallback);
117
+ return trimmed;
118
+ }
119
+ function optionalText(value) {
120
+ return typeof value === 'string' ? value.trim() : '';
121
+ }
122
+ //# sourceMappingURL=codex-hook-output-builders.js.map
@@ -1,2 +1,2 @@
1
- export declare function normalizeCodexHookOutput(name: unknown, result?: any): any;
1
+ export declare function normalizeCodexHookOutput(name: unknown, result?: any): import("./codex-hook-output-builders.js").CodexHookOutput;
2
2
  //# sourceMappingURL=codex-hook-output-normalizer.d.ts.map