sneakoscope 1.18.13 → 1.18.14
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 +2 -2
- 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/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +8 -7
- package/dist/commands/mad-sks.d.ts +1 -0
- package/dist/core/codex/codex-config-eperm-repair.d.ts +1 -0
- package/dist/core/codex/codex-config-eperm-repair.js +20 -2
- package/dist/core/codex/codex-config-readability.js +31 -1
- package/dist/core/codex/codex-project-config-policy.d.ts +23 -0
- package/dist/core/codex/codex-project-config-policy.js +191 -8
- package/dist/core/commands/mad-sks-command.d.ts +1 -0
- package/dist/core/fsx.d.ts +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/preflight/parallel-preflight-engine.d.ts +1 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-config-load-probe.mjs +245 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
|
|
|
16
16
|
|
|
17
17
|
## Current Release
|
|
18
18
|
|
|
19
|
-
SKS **1.18.
|
|
19
|
+
SKS **1.18.14** is the Zellij-only interactive runtime release: actual Codex CLI config-load truth, fake Codex EPERM fixtures, doctor readiness matrix proof, `sks mad repair-config`, safer project-local config splitting, Codex 0.135 compatibility gates, and official Fast mode CLI override proof are wired into release checks. Doctor now refuses Ready yes without Codex config-load evidence, MAD blocks launch before unreadable config can crash Codex, and interactive MAD/lane UI requires Zellij with no removed-runtime fallback.
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
sks mad-sks plan --target-root <path> --json
|
|
@@ -699,7 +699,7 @@ npm run release:check
|
|
|
699
699
|
npm run publish:dry
|
|
700
700
|
```
|
|
701
701
|
|
|
702
|
-
`release:check` runs the 1.18.
|
|
702
|
+
`release:check` runs the 1.18.14 Zellij-only closure DAG, writes a source digest stamp under `.sneakoscope/reports/`, then refreshes release readiness so publish commands can verify the same stamp. The DAG preserves the 1.18 baseline gates and adds patch swarm runtime truth, transaction journaling, serial conflict rebase, strict strategy-to-patch proof, rollback command proof, Native CLI Session Swarm 5/10/20-process proof, Real Worker Backend Router proof, Codex child overlap proof, model-authored patch-envelope separation, Zellij layout/pane/screen proof, no-subagent-scaling proof, Fast mode default/worker/Codex/MAD propagation proof, Appshots attachment provenance, MCP runtime overlap evidence, Codex 0.134/0.135 runner truth, task graph expansion, schema-bound follow-up work, actual Agent/Team/Research/QA route blackboxes, scheduler proof hardening, Source Intelligence propagation, and Goal mode propagation checks. Broader live gates remain explicit scripts such as `release:real-check`; real Codex patch smoke, real Codex parallel worker proof, and real Zellij proof are optional unless their `SKS_REQUIRE_REAL_*` or `SKS_REQUIRE_ZELLIJ=1` environment variables are set. Generate the human-readable registry with `sks features inventory --write-docs`. Plain `npm publish` uses the `latest` dist-tag. npm's `prepublishOnly` verifies the fresh release stamp instead of rerunning the full gate, and `prepack` only rebuilds `dist`; publish no longer repeats the expensive release suite during packaging. `npm run publish:dry` remains the explicit dry-run helper.
|
|
703
703
|
|
|
704
704
|
Version bumps are manual. Run `sks versioning bump` only when preparing release metadata; SKS will not create `.git/hooks/pre-commit` or auto-bump during ordinary commits.
|
|
705
705
|
|
|
@@ -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.18.
|
|
7
|
+
Some("--version") => println!("sks-rs 1.18.14"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build-stamp.v1",
|
|
3
3
|
"package_name": "sneakoscope",
|
|
4
|
-
"package_version": "1.18.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "1.18.14",
|
|
5
|
+
"source_digest": "b13a96880fa5b57ba4b3649ac5bedbcca941d871f92f9a4bf396ab20fd9c8b4b",
|
|
6
|
+
"source_file_count": 1679,
|
|
7
|
+
"built_at_source_time": 1780051095178
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
package/dist/build-manifest.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build.v2",
|
|
3
|
-
"version": "1.18.
|
|
4
|
-
"package_version": "1.18.
|
|
3
|
+
"version": "1.18.14",
|
|
4
|
+
"package_version": "1.18.14",
|
|
5
5
|
"typescript": true,
|
|
6
|
-
"mjs_runtime_files":
|
|
6
|
+
"mjs_runtime_files": 1,
|
|
7
7
|
"compiled_file_count": 972,
|
|
8
8
|
"compiled_js_count": 486,
|
|
9
9
|
"compiled_dts_count": 486,
|
|
10
|
-
"source_digest": "
|
|
11
|
-
"source_file_count":
|
|
12
|
-
"source_files_hash": "
|
|
13
|
-
"source_list_hash": "
|
|
10
|
+
"source_digest": "b13a96880fa5b57ba4b3649ac5bedbcca941d871f92f9a4bf396ab20fd9c8b4b",
|
|
11
|
+
"source_file_count": 1679,
|
|
12
|
+
"source_files_hash": "737317372a19030a70f432942ce650900c05270a2a1d1c49281d135a1b15e66c",
|
|
13
|
+
"source_list_hash": "737317372a19030a70f432942ce650900c05270a2a1d1c49281d135a1b15e66c",
|
|
14
14
|
"src_mjs_runtime_files": 0,
|
|
15
15
|
"dist_stamp_schema": "sks.dist-build-stamp.v1",
|
|
16
16
|
"files": [
|
|
@@ -985,6 +985,7 @@
|
|
|
985
985
|
"core/zellij/zellij-pane-proof.js",
|
|
986
986
|
"core/zellij/zellij-screen-proof.d.ts",
|
|
987
987
|
"core/zellij/zellij-screen-proof.js",
|
|
988
|
+
"scripts/codex-config-load-probe.mjs",
|
|
988
989
|
"scripts/release-parallel-check.d.ts",
|
|
989
990
|
"scripts/release-parallel-check.js",
|
|
990
991
|
"vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json",
|
|
@@ -113,6 +113,7 @@ export declare function run(_command: any, args?: any): Promise<void | {
|
|
|
113
113
|
};
|
|
114
114
|
blockers: string[];
|
|
115
115
|
};
|
|
116
|
+
structure_repairs: any[];
|
|
116
117
|
repair_actions: any[];
|
|
117
118
|
after: import("../core/codex/codex-config-readability.js").CodexConfigReadabilityReport;
|
|
118
119
|
tcc_risk: boolean;
|
|
@@ -49,6 +49,7 @@ export declare function repairCodexConfigEperm(rootInput?: string, opts?: any):
|
|
|
49
49
|
};
|
|
50
50
|
blockers: string[];
|
|
51
51
|
};
|
|
52
|
+
structure_repairs: any[];
|
|
52
53
|
repair_actions: any[];
|
|
53
54
|
after: import("./codex-config-readability.js").CodexConfigReadabilityReport;
|
|
54
55
|
tcc_risk: boolean;
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import fsp from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { nowIso, readText, runProcess, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
5
|
import { inspectCodexConfigReadability } from './codex-config-readability.js';
|
|
5
|
-
import { splitCodexProjectConfigPolicy } from './codex-project-config-policy.js';
|
|
6
|
+
import { repairCodexConfigStructure, splitCodexProjectConfigPolicy } from './codex-project-config-policy.js';
|
|
6
7
|
export const CODEX_CONFIG_EPERM_REPAIR_SCHEMA = 'sks.codex-config-eperm-repair.v1';
|
|
7
8
|
export async function repairCodexConfigEperm(rootInput = process.cwd(), opts = {}) {
|
|
8
9
|
const root = path.resolve(rootInput || process.cwd());
|
|
9
10
|
const reportPath = opts.reportPath || path.join(root, '.sneakoscope', 'reports', 'codex-config-eperm-repair.json');
|
|
10
11
|
const configPath = path.resolve(opts.configPath || path.join(root, '.codex', 'config.toml'));
|
|
12
|
+
const codexHome = path.resolve(opts.codexHome || process.env.CODEX_HOME || path.join(process.env.HOME || os.homedir(), '.codex'));
|
|
13
|
+
const codexHomeConfigPath = path.join(codexHome, 'config.toml');
|
|
11
14
|
const before = await inspectCodexConfigReadability(root, { ...opts, configPath, writeReport: false });
|
|
12
|
-
|
|
15
|
+
// Structural recovery FIRST: hoist machine-local keys that a prior buggy move
|
|
16
|
+
// absorbed into a table back to the root, on both the project config and the
|
|
17
|
+
// global CODEX_HOME config (the file Codex actually loads). Runs before the
|
|
18
|
+
// splitter so recovered keys can then be migrated cleanly.
|
|
19
|
+
const structureRepairs = [];
|
|
20
|
+
if (opts.fix === true) {
|
|
21
|
+
structureRepairs.push({ scope: 'project', ...(await repairCodexConfigStructure(configPath, { apply: true })) });
|
|
22
|
+
if (path.resolve(codexHomeConfigPath) !== path.resolve(configPath)) {
|
|
23
|
+
structureRepairs.push({ scope: 'codex_home', ...(await repairCodexConfigStructure(codexHomeConfigPath, { apply: true })) });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const policy = await splitCodexProjectConfigPolicy(root, { ...opts, configPath, codexHome, apply: opts.fix === true, writeReport: false });
|
|
13
27
|
const repairActions = opts.fix === true ? await runScopedRepairs(configPath, before.blockers) : [];
|
|
14
28
|
const after = await inspectCodexConfigReadability(root, { ...opts, configPath, writeReport: false });
|
|
15
29
|
const blockers = [...new Set([...(policy.blockers || []), ...after.blockers])];
|
|
@@ -25,6 +39,7 @@ export async function repairCodexConfigEperm(rootInput = process.cwd(), opts = {
|
|
|
25
39
|
fix: opts.fix === true,
|
|
26
40
|
before,
|
|
27
41
|
policy,
|
|
42
|
+
structure_repairs: structureRepairs,
|
|
28
43
|
repair_actions: repairActions,
|
|
29
44
|
after,
|
|
30
45
|
tcc_risk: tccRisk(root),
|
|
@@ -32,6 +47,9 @@ export async function repairCodexConfigEperm(rootInput = process.cwd(), opts = {
|
|
|
32
47
|
blockers,
|
|
33
48
|
operator_actions: [
|
|
34
49
|
...(after.operator_actions || []),
|
|
50
|
+
...structureRepairs
|
|
51
|
+
.filter((repair) => repair.applied && repair.hoisted_keys?.length)
|
|
52
|
+
.map((repair) => `Recovered misplaced machine-local keys (${repair.hoisted_keys.join(', ')}) back to the top of ${repair.scope === 'codex_home' ? 'CODEX_HOME' : 'project'} config; backup at ${repair.backup_path}.`),
|
|
35
53
|
...(tccProbable ? ['macOS probable TCC block: grant Full Disk Access and Files and Folders permissions to Warp/Terminal/iTerm, Codex app, and the Codex CLI launch context, then rerun `sks mad repair-config --apply`.'] : [])
|
|
36
54
|
]
|
|
37
55
|
};
|
|
@@ -175,6 +175,8 @@ function operatorActions(blockers) {
|
|
|
175
175
|
const actions = new Set();
|
|
176
176
|
if (blockers.some((item) => /^missing_/.test(item)))
|
|
177
177
|
actions.add('Run `sks doctor --fix` to regenerate the managed Codex project config, then rerun the preflight.');
|
|
178
|
+
if (blockers.includes('codex_cli_config_toml_parse_error'))
|
|
179
|
+
actions.add('Run `sks doctor --fix` (or `sks mad repair-config --apply`) to hoist misplaced machine-local keys back to the top of the Codex config and restore a loadable config.toml.');
|
|
178
180
|
if (blockers.includes('codex_cli_config_eperm'))
|
|
179
181
|
actions.add('Run `sks mad repair-config --apply`; if it still fails on macOS, grant Full Disk Access/Files and Folders access to the launching terminal, Warp, iTerm, Terminal, Codex app, or Codex CLI context.');
|
|
180
182
|
if (blockers.includes('EPERM') || blockers.includes('tcc_possible'))
|
|
@@ -194,6 +196,24 @@ function operatorActions(blockers) {
|
|
|
194
196
|
function errorDetail(err) {
|
|
195
197
|
return { name: err?.name || 'Error', code: err?.code || '', message: err?.message || String(err) };
|
|
196
198
|
}
|
|
199
|
+
function resolveCodexConfigLoadProbe() {
|
|
200
|
+
// Prefer the packaged copy under dist/scripts (shipped via the `dist` files
|
|
201
|
+
// allowlist); fall back to the repo-root scripts/ copy for local development.
|
|
202
|
+
const candidates = [
|
|
203
|
+
path.join(packageRoot(), 'dist', 'scripts', 'codex-config-load-probe.mjs'),
|
|
204
|
+
path.join(packageRoot(), 'scripts', 'codex-config-load-probe.mjs')
|
|
205
|
+
];
|
|
206
|
+
for (const candidate of candidates) {
|
|
207
|
+
try {
|
|
208
|
+
if (fs.existsSync(candidate))
|
|
209
|
+
return candidate;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// ignore and try the next candidate
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
197
217
|
async function codexCliConfigLoadCheck(root, configPath, opts = {}) {
|
|
198
218
|
if (!opts.codexProbe && !opts.actualCodex && !opts.codexBin) {
|
|
199
219
|
return {
|
|
@@ -203,7 +223,17 @@ async function codexCliConfigLoadCheck(root, configPath, opts = {}) {
|
|
|
203
223
|
detail: { integration_optional: true, blockers: [] }
|
|
204
224
|
};
|
|
205
225
|
}
|
|
206
|
-
const script =
|
|
226
|
+
const script = resolveCodexConfigLoadProbe();
|
|
227
|
+
if (!script) {
|
|
228
|
+
// The probe ships in dist/scripts; if it is genuinely absent (packaging gap),
|
|
229
|
+
// do not block MAD preflight on an unverifiable check — degrade gracefully.
|
|
230
|
+
return {
|
|
231
|
+
name: 'actual_codex_cli_config_load',
|
|
232
|
+
ok: true,
|
|
233
|
+
status: 'integration_optional_probe_missing',
|
|
234
|
+
detail: { integration_optional: true, blockers: [] }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
207
237
|
const args = [script, '--root', root, '--config', configPath, '--json'];
|
|
208
238
|
if (opts.actualCodex !== false)
|
|
209
239
|
args.push('--actual-codex');
|
|
@@ -41,4 +41,27 @@ export declare function splitCodexProjectConfigPolicy(rootInput?: string, opts?:
|
|
|
41
41
|
};
|
|
42
42
|
blockers: string[];
|
|
43
43
|
}>;
|
|
44
|
+
export declare function repairCodexConfigStructure(configPathInput: string, opts?: any): Promise<{
|
|
45
|
+
config_path: string;
|
|
46
|
+
ok: boolean;
|
|
47
|
+
status: string;
|
|
48
|
+
changed: boolean;
|
|
49
|
+
applied: boolean;
|
|
50
|
+
hoisted_keys: never[];
|
|
51
|
+
backup_path: null;
|
|
52
|
+
parse_smoke?: never;
|
|
53
|
+
} | {
|
|
54
|
+
config_path: string;
|
|
55
|
+
ok: boolean;
|
|
56
|
+
status: string;
|
|
57
|
+
changed: boolean;
|
|
58
|
+
applied: boolean;
|
|
59
|
+
hoisted_keys: string[];
|
|
60
|
+
backup_path: string | null;
|
|
61
|
+
parse_smoke: {
|
|
62
|
+
ok: boolean;
|
|
63
|
+
unterminated_multiline_string: boolean;
|
|
64
|
+
invalid_table_header: string | null;
|
|
65
|
+
};
|
|
66
|
+
}>;
|
|
44
67
|
//# sourceMappingURL=codex-project-config-policy.d.ts.map
|
|
@@ -32,6 +32,30 @@ export async function splitCodexProjectConfigPolicy(rootInput = process.cwd(), o
|
|
|
32
32
|
const configPath = path.resolve(opts.configPath || path.join(root, '.codex', 'config.toml'));
|
|
33
33
|
const codexHome = path.resolve(opts.codexHome || process.env.CODEX_HOME || path.join(process.env.HOME || os.homedir(), '.codex'));
|
|
34
34
|
const reportPath = opts.reportPath || path.join(root, '.sneakoscope', 'reports', 'codex-project-config-policy.json');
|
|
35
|
+
const codexHomeConfigPath = path.join(codexHome, 'config.toml');
|
|
36
|
+
// Guard: never split the global Codex home config against itself. When `sks`
|
|
37
|
+
// runs from (or near) the home directory the project config can resolve to the
|
|
38
|
+
// same file as the move target. Splitting it would strip machine-local keys and
|
|
39
|
+
// re-append them, corrupting the file. Treat this as a no-op.
|
|
40
|
+
if (await isSameFile(configPath, codexHomeConfigPath)) {
|
|
41
|
+
const report = {
|
|
42
|
+
schema: CODEX_PROJECT_CONFIG_POLICY_SCHEMA,
|
|
43
|
+
generated_at: nowIso(),
|
|
44
|
+
root,
|
|
45
|
+
config_path: configPath,
|
|
46
|
+
codex_home: codexHome,
|
|
47
|
+
ok: true,
|
|
48
|
+
status: 'project_config_is_codex_home_noop',
|
|
49
|
+
changed: false,
|
|
50
|
+
moved_keys: [],
|
|
51
|
+
moved_tables: [],
|
|
52
|
+
actions: [],
|
|
53
|
+
blockers: []
|
|
54
|
+
};
|
|
55
|
+
if (opts.writeReport !== false)
|
|
56
|
+
await writeJsonAtomic(reportPath, { ...report, report_path: reportPath });
|
|
57
|
+
return report;
|
|
58
|
+
}
|
|
35
59
|
const original = await readText(configPath, null);
|
|
36
60
|
if (original === null) {
|
|
37
61
|
const report = {
|
|
@@ -70,16 +94,12 @@ export async function splitCodexProjectConfigPolicy(rootInput = process.cwd(), o
|
|
|
70
94
|
}
|
|
71
95
|
if (opts.apply && split.machine_text.trim()) {
|
|
72
96
|
await ensureDir(codexHome);
|
|
73
|
-
userConfigPath =
|
|
97
|
+
userConfigPath = codexHomeConfigPath;
|
|
74
98
|
const currentUser = await readText(userConfigPath, '');
|
|
75
99
|
const dedupedUser = removeConfigIds(String(currentUser || ''), configIds(split.machine_text));
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
split.machine_text.trim(),
|
|
80
|
-
''
|
|
81
|
-
].join('\n');
|
|
82
|
-
await writeTextAtomic(userConfigPath, `${dedupedUser.replace(/\s+$/, '')}${movedBlock}`.replace(/^\n+/, ''));
|
|
100
|
+
const commentLine = `# SKS moved machine-local Codex config from ${path.relative(root, configPath) || configPath} at ${nowIso()}`;
|
|
101
|
+
const mergedUser = mergeMachineLocalIntoUserConfig(dedupedUser, split.machine_text.trim(), commentLine);
|
|
102
|
+
await writeTextAtomic(userConfigPath, mergedUser);
|
|
83
103
|
actions.push('machine_local_keys_moved_to_codex_home_config');
|
|
84
104
|
}
|
|
85
105
|
if (profileName)
|
|
@@ -113,6 +133,116 @@ export async function splitCodexProjectConfigPolicy(rootInput = process.cwd(), o
|
|
|
113
133
|
await writeJsonAtomic(reportPath, { ...report, report_path: reportPath });
|
|
114
134
|
return report;
|
|
115
135
|
}
|
|
136
|
+
// Recovery pass for already-corrupted configs. The pre-fix mover appended
|
|
137
|
+
// machine-local top-level keys after the last [table], so TOML absorbed them
|
|
138
|
+
// into that table (e.g. `notify`/`model_provider` landing inside
|
|
139
|
+
// `[mcp_servers.*.env]`, which Codex rejects with
|
|
140
|
+
// `invalid type: sequence, expected a string`). The splitter cannot recover this
|
|
141
|
+
// because it now sees those lines as table members, not top-level keys. This pass
|
|
142
|
+
// hoists them back above the first table so Codex can load the config again.
|
|
143
|
+
export async function repairCodexConfigStructure(configPathInput, opts = {}) {
|
|
144
|
+
const configPath = path.resolve(configPathInput);
|
|
145
|
+
const original = await readText(configPath, null);
|
|
146
|
+
if (original === null) {
|
|
147
|
+
return { config_path: configPath, ok: true, status: 'config_missing', changed: false, applied: false, hoisted_keys: [], backup_path: null };
|
|
148
|
+
}
|
|
149
|
+
const hoist = hoistMisplacedMachineLocalKeys(String(original));
|
|
150
|
+
let backupPath = null;
|
|
151
|
+
if (opts.apply && hoist.changed) {
|
|
152
|
+
backupPath = `${configPath}.struct-bak-${Date.now().toString(36)}`;
|
|
153
|
+
await ensureDir(path.dirname(configPath));
|
|
154
|
+
await fsp.copyFile(configPath, backupPath);
|
|
155
|
+
await writeTextAtomic(configPath, hoist.text);
|
|
156
|
+
}
|
|
157
|
+
const parseSmoke = tomlRewriteSmoke(hoist.text);
|
|
158
|
+
return {
|
|
159
|
+
config_path: configPath,
|
|
160
|
+
ok: parseSmoke.ok,
|
|
161
|
+
status: hoist.changed ? (opts.apply ? 'structure_repaired' : 'structure_repair_available') : 'structure_ok',
|
|
162
|
+
changed: hoist.changed,
|
|
163
|
+
applied: opts.apply === true && hoist.changed,
|
|
164
|
+
hoisted_keys: hoist.hoisted_keys,
|
|
165
|
+
backup_path: backupPath,
|
|
166
|
+
parse_smoke: parseSmoke
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function hoistMisplacedMachineLocalKeys(text) {
|
|
170
|
+
const blocks = tomlBlocks(text);
|
|
171
|
+
const preamble = [];
|
|
172
|
+
const tables = [];
|
|
173
|
+
const hoisted = [];
|
|
174
|
+
const hoistedKeys = [];
|
|
175
|
+
for (const block of blocks) {
|
|
176
|
+
if (!block.table) {
|
|
177
|
+
preamble.push(block.text);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const lines = block.text.split('\n');
|
|
181
|
+
const header = lines[0];
|
|
182
|
+
// `mcp_servers.*` / `*.env` tables never legitimately contain machine-local
|
|
183
|
+
// Codex root keys; treat keys found there (or anything after an absorbed
|
|
184
|
+
// SKS-moved comment) as misplaced.
|
|
185
|
+
const corruptionProne = /^mcp_servers(\.|$)/.test(block.table) || /\.env$/.test(block.table) || block.table === 'env';
|
|
186
|
+
const kept = [header];
|
|
187
|
+
let sawMovedComment = false;
|
|
188
|
+
let i = 1;
|
|
189
|
+
while (i < lines.length) {
|
|
190
|
+
const line = lines[i];
|
|
191
|
+
if (/^\s*#\s*SKS moved machine-local Codex config/i.test(line)) {
|
|
192
|
+
sawMovedComment = true;
|
|
193
|
+
i += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const key = topLevelKey(line);
|
|
197
|
+
const isMachineKey = Boolean(key) && MACHINE_LOCAL_TOP_LEVEL_KEYS.has(key);
|
|
198
|
+
if (isMachineKey && (corruptionProne || sawMovedComment)) {
|
|
199
|
+
const span = captureAssignmentSpan(lines, i);
|
|
200
|
+
for (const spanned of span.lines)
|
|
201
|
+
hoisted.push(spanned);
|
|
202
|
+
hoistedKeys.push(key);
|
|
203
|
+
i = span.next;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
kept.push(line);
|
|
207
|
+
i += 1;
|
|
208
|
+
}
|
|
209
|
+
tables.push(kept.join('\n'));
|
|
210
|
+
}
|
|
211
|
+
if (!hoisted.length)
|
|
212
|
+
return { changed: false, text, hoisted_keys: [] };
|
|
213
|
+
const head = [preamble.join('\n').trim(), hoisted.join('\n').trim()].filter(Boolean).join('\n');
|
|
214
|
+
const sections = [head, ...tables.map((table) => table.trim())].filter(Boolean);
|
|
215
|
+
return { changed: true, text: normalizeProjectText(sections.join('\n\n')), hoisted_keys: [...new Set(hoistedKeys)] };
|
|
216
|
+
}
|
|
217
|
+
// Capture a TOML assignment that may span multiple lines (multiline arrays
|
|
218
|
+
// `[ ... ]` or triple-quoted strings) so hoisting never splits a value.
|
|
219
|
+
function captureAssignmentSpan(lines, start) {
|
|
220
|
+
const first = lines[start] ?? '';
|
|
221
|
+
const collected = [first];
|
|
222
|
+
let next = start + 1;
|
|
223
|
+
let triple = updateMultilineState(first, null);
|
|
224
|
+
let bracketDepth = bracketDelta(first);
|
|
225
|
+
while ((triple || bracketDepth > 0) && next < lines.length) {
|
|
226
|
+
const line = lines[next] ?? '';
|
|
227
|
+
collected.push(line);
|
|
228
|
+
triple = updateMultilineState(line, triple);
|
|
229
|
+
bracketDepth += bracketDelta(line);
|
|
230
|
+
next += 1;
|
|
231
|
+
}
|
|
232
|
+
return { lines: collected, next };
|
|
233
|
+
}
|
|
234
|
+
function bracketDelta(line) {
|
|
235
|
+
const noComment = stripCommentOutsideQuotes(String(line));
|
|
236
|
+
const noStrings = noComment.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, '');
|
|
237
|
+
let delta = 0;
|
|
238
|
+
for (const ch of noStrings) {
|
|
239
|
+
if (ch === '[')
|
|
240
|
+
delta += 1;
|
|
241
|
+
else if (ch === ']')
|
|
242
|
+
delta -= 1;
|
|
243
|
+
}
|
|
244
|
+
return delta;
|
|
245
|
+
}
|
|
116
246
|
function splitProjectToml(text) {
|
|
117
247
|
const blocks = tomlBlocks(text);
|
|
118
248
|
const kept = [];
|
|
@@ -269,6 +399,59 @@ function configIds(text) {
|
|
|
269
399
|
}
|
|
270
400
|
return ids;
|
|
271
401
|
}
|
|
402
|
+
async function isSameFile(a, b) {
|
|
403
|
+
const ra = path.resolve(a);
|
|
404
|
+
const rb = path.resolve(b);
|
|
405
|
+
if (ra === rb)
|
|
406
|
+
return true;
|
|
407
|
+
try {
|
|
408
|
+
const [realA, realB] = await Promise.all([fsp.realpath(ra), fsp.realpath(rb)]);
|
|
409
|
+
return realA === realB;
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// Merge machine-local Codex config into the user (CODEX_HOME) config while
|
|
416
|
+
// preserving TOML structure: bare top-level keys must appear before any
|
|
417
|
+
// `[table]` header, otherwise they are parsed as members of the preceding
|
|
418
|
+
// table. Appending moved keys blindly at end-of-file corrupted configs
|
|
419
|
+
// (e.g. `notify`/`model_provider` landing inside `[mcp_servers.*.env]`).
|
|
420
|
+
function mergeMachineLocalIntoUserConfig(userText, machineText, commentLine) {
|
|
421
|
+
const preamble = [];
|
|
422
|
+
const tables = [];
|
|
423
|
+
collectTomlSections(userText, preamble, tables);
|
|
424
|
+
const movedPreamble = [];
|
|
425
|
+
const movedTables = [];
|
|
426
|
+
collectTomlSections(machineText, movedPreamble, movedTables);
|
|
427
|
+
const head = [];
|
|
428
|
+
const existingHead = preamble.join('\n').trim();
|
|
429
|
+
if (existingHead)
|
|
430
|
+
head.push(existingHead);
|
|
431
|
+
const movedHead = movedPreamble.join('\n').trim();
|
|
432
|
+
if (commentLine && (movedHead || movedTables.length))
|
|
433
|
+
head.push(commentLine);
|
|
434
|
+
if (movedHead)
|
|
435
|
+
head.push(movedHead);
|
|
436
|
+
const sections = [];
|
|
437
|
+
const headText = head.join('\n').trim();
|
|
438
|
+
if (headText)
|
|
439
|
+
sections.push(headText);
|
|
440
|
+
for (const table of [...tables, ...movedTables]) {
|
|
441
|
+
const trimmed = table.trim();
|
|
442
|
+
if (trimmed)
|
|
443
|
+
sections.push(trimmed);
|
|
444
|
+
}
|
|
445
|
+
return normalizeProjectText(sections.join('\n\n'));
|
|
446
|
+
}
|
|
447
|
+
function collectTomlSections(text, preamble, tables) {
|
|
448
|
+
for (const block of tomlBlocks(text)) {
|
|
449
|
+
if (block.table)
|
|
450
|
+
tables.push(block.text);
|
|
451
|
+
else
|
|
452
|
+
preamble.push(block.text);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
272
455
|
function removeConfigIds(text, ids) {
|
|
273
456
|
const kept = [];
|
|
274
457
|
for (const block of tomlBlocks(text)) {
|
|
@@ -113,6 +113,7 @@ export declare function madHighCommand(args?: any, deps?: any): Promise<void | {
|
|
|
113
113
|
};
|
|
114
114
|
blockers: string[];
|
|
115
115
|
};
|
|
116
|
+
structure_repairs: any[];
|
|
116
117
|
repair_actions: any[];
|
|
117
118
|
after: import("../codex/codex-config-readability.js").CodexConfigReadabilityReport;
|
|
118
119
|
tcc_risk: boolean;
|
package/dist/core/fsx.d.ts
CHANGED
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 = '1.18.
|
|
8
|
+
export const PACKAGE_VERSION = '1.18.14';
|
|
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() {
|
|
@@ -87,6 +87,7 @@ export declare function runCodexLaunchPreflight(rootInput?: string, opts?: any):
|
|
|
87
87
|
};
|
|
88
88
|
blockers: string[];
|
|
89
89
|
};
|
|
90
|
+
structure_repairs: any[];
|
|
90
91
|
repair_actions: any[];
|
|
91
92
|
after: import("../codex/codex-config-readability.js").CodexConfigReadabilityReport;
|
|
92
93
|
tcc_risk: boolean;
|
package/dist/core/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "1.18.
|
|
1
|
+
export declare const PACKAGE_VERSION = "1.18.14";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '1.18.
|
|
1
|
+
export const PACKAGE_VERSION = '1.18.14';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const root = path.resolve(readOption('--root', process.cwd()));
|
|
8
|
+
const configPath = path.resolve(readOption('--config', path.join(root, '.codex', 'config.toml')));
|
|
9
|
+
const json = args.includes('--json');
|
|
10
|
+
const actualRequested = args.includes('--actual-codex')
|
|
11
|
+
|| process.env.SKS_TEST_REAL_CODEX_CONFIG_LOAD === '1'
|
|
12
|
+
|| Boolean(readOption('--codex-bin', ''));
|
|
13
|
+
const actualRequired = args.includes('--require-actual-codex')
|
|
14
|
+
|| process.env.SKS_REQUIRE_REAL_CODEX_CONFIG_LOAD === '1';
|
|
15
|
+
const codexBin = readOption('--codex-bin', process.env.SKS_CODEX_CONFIG_PROBE_BIN || 'codex');
|
|
16
|
+
const outputLastMessage = path.resolve(readOption(
|
|
17
|
+
'--output-last-message',
|
|
18
|
+
path.join(root, '.sneakoscope', 'reports', 'codex-config-load-probe-output.json')
|
|
19
|
+
));
|
|
20
|
+
|
|
21
|
+
const report = {
|
|
22
|
+
schema: 'sks.codex-config-load-probe.v2',
|
|
23
|
+
generated_at: new Date().toISOString(),
|
|
24
|
+
root,
|
|
25
|
+
config_path: configPath,
|
|
26
|
+
ok: false,
|
|
27
|
+
checks: [],
|
|
28
|
+
blockers: [],
|
|
29
|
+
warnings: [],
|
|
30
|
+
integration_optional: !actualRequired,
|
|
31
|
+
actual_codex_requested: actualRequested,
|
|
32
|
+
actual_codex_required: actualRequired
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
await check('node_read', async () => {
|
|
36
|
+
const text = await fs.readFile(configPath, 'utf8');
|
|
37
|
+
return { bytes: Buffer.byteLength(text) };
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const child = spawnSync(process.execPath, ['-e', 'require("fs").readFileSync(process.argv[1], "utf8")', configPath], {
|
|
41
|
+
cwd: root,
|
|
42
|
+
encoding: 'utf8'
|
|
43
|
+
});
|
|
44
|
+
pushCheck({
|
|
45
|
+
name: 'spawned_node_child_read',
|
|
46
|
+
ok: child.status === 0,
|
|
47
|
+
exit_code: child.status,
|
|
48
|
+
stdout_tail: tail(child.stdout),
|
|
49
|
+
stderr_tail: tail(child.stderr),
|
|
50
|
+
signals: classifyText(`${child.stderr}\n${child.stdout}`)
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (actualRequested) {
|
|
54
|
+
await fs.mkdir(path.dirname(outputLastMessage), { recursive: true }).catch(() => {});
|
|
55
|
+
await fs.rm(outputLastMessage, { force: true }).catch(() => {});
|
|
56
|
+
const codexArgs = [
|
|
57
|
+
'exec',
|
|
58
|
+
'--skip-git-repo-check',
|
|
59
|
+
'--sandbox',
|
|
60
|
+
'read-only',
|
|
61
|
+
'--ignore-rules',
|
|
62
|
+
'--output-last-message',
|
|
63
|
+
outputLastMessage,
|
|
64
|
+
'Reply exactly SKS_CONFIG_LOAD_PROBE_OK.'
|
|
65
|
+
];
|
|
66
|
+
const command = commandForCodex(codexBin, codexArgs);
|
|
67
|
+
const result = spawnSync(command.command, command.args, {
|
|
68
|
+
cwd: root,
|
|
69
|
+
env: probeEnv(process.env),
|
|
70
|
+
encoding: 'utf8',
|
|
71
|
+
timeout: Number(process.env.SKS_CODEX_CONFIG_LOAD_TIMEOUT_MS || 20000),
|
|
72
|
+
maxBuffer: 1024 * 1024
|
|
73
|
+
});
|
|
74
|
+
const outputLastText = await readTextIfExists(outputLastMessage);
|
|
75
|
+
const observedText = `${result.stderr || ''}\n${result.stdout || ''}\n${outputLastText || ''}`;
|
|
76
|
+
const probeOkObserved = /SKS_CONFIG_LOAD_PROBE_OK/.test(observedText);
|
|
77
|
+
const signals = classifyText(observedText, { configLoaded: probeOkObserved });
|
|
78
|
+
const configFailure = signals.blockers.length > 0;
|
|
79
|
+
const unavailable = result.error?.code === 'ENOENT';
|
|
80
|
+
const timeoutAfterProbeOk = result.error?.code === 'ETIMEDOUT' && probeOkObserved;
|
|
81
|
+
const executionError = Boolean(result.error) && !timeoutAfterProbeOk;
|
|
82
|
+
const nonConfigFailure = (result.status !== 0 || executionError) && !configFailure && !unavailable;
|
|
83
|
+
const passed = (result.status === 0 && !executionError) || (probeOkObserved && !configFailure);
|
|
84
|
+
pushCheck({
|
|
85
|
+
name: 'actual_codex_cli_config_load',
|
|
86
|
+
ok: passed,
|
|
87
|
+
status: passed
|
|
88
|
+
? timeoutAfterProbeOk
|
|
89
|
+
? 'passed_after_probe_ok_timeout'
|
|
90
|
+
: 'passed'
|
|
91
|
+
: unavailable
|
|
92
|
+
? 'integration_optional_unavailable'
|
|
93
|
+
: configFailure
|
|
94
|
+
? 'failed_config_load'
|
|
95
|
+
: 'non_config_failure_after_config_load',
|
|
96
|
+
integration_optional: !actualRequired && (unavailable || nonConfigFailure),
|
|
97
|
+
exit_code: result.status,
|
|
98
|
+
signal: result.signal,
|
|
99
|
+
error: result.error ? { code: result.error.code, message: result.error.message } : null,
|
|
100
|
+
command: {
|
|
101
|
+
executable: redact(codexBin),
|
|
102
|
+
args: codexArgs.map(redact)
|
|
103
|
+
},
|
|
104
|
+
env_keys: Object.keys(probeEnv(process.env)).sort(),
|
|
105
|
+
stdout_tail: tail(result.stdout),
|
|
106
|
+
stderr_tail: tail(result.stderr),
|
|
107
|
+
output_last_message: outputLastMessage,
|
|
108
|
+
output_last_message_tail: tail(outputLastText),
|
|
109
|
+
probe_ok_observed: probeOkObserved,
|
|
110
|
+
signals
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
pushCheck({
|
|
114
|
+
name: 'actual_codex_cli_config_load',
|
|
115
|
+
ok: true,
|
|
116
|
+
status: 'integration_optional_not_requested',
|
|
117
|
+
integration_optional: true,
|
|
118
|
+
command: { executable: redact(codexBin), args: [] },
|
|
119
|
+
signals: { blockers: [], flags: {} }
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
report.ok = report.checks.every((check) => {
|
|
124
|
+
if (check.ok) return true;
|
|
125
|
+
return check.integration_optional === true && !actualRequired;
|
|
126
|
+
}) && report.blockers.length === 0;
|
|
127
|
+
|
|
128
|
+
if (json) console.log(JSON.stringify(report, null, 2));
|
|
129
|
+
else {
|
|
130
|
+
console.log(report.ok
|
|
131
|
+
? `Codex config load probe ok: ${configPath}`
|
|
132
|
+
: `Codex config load probe failed: ${report.blockers.join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
if (!report.ok) process.exitCode = 1;
|
|
135
|
+
|
|
136
|
+
async function check(name, fn) {
|
|
137
|
+
try {
|
|
138
|
+
pushCheck({ name, ok: true, detail: await fn(), signals: { blockers: [], flags: {} } });
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const signals = classifyText(`${err?.code || ''}\n${err?.message || String(err)}`);
|
|
141
|
+
pushCheck({
|
|
142
|
+
name,
|
|
143
|
+
ok: false,
|
|
144
|
+
error: { code: err?.code || '', message: err?.message || String(err) },
|
|
145
|
+
signals
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function pushCheck(check) {
|
|
151
|
+
report.checks.push(check);
|
|
152
|
+
for (const warning of check.signals?.warnings || []) {
|
|
153
|
+
if (!report.warnings.includes(warning)) report.warnings.push(warning);
|
|
154
|
+
}
|
|
155
|
+
if (check.ok) return;
|
|
156
|
+
for (const blocker of check.signals?.blockers || classifyText(`${check.stderr_tail || ''}\n${check.error?.message || ''}`).blockers) {
|
|
157
|
+
if (!report.blockers.includes(blocker)) report.blockers.push(blocker);
|
|
158
|
+
}
|
|
159
|
+
if (!check.integration_optional && check.name === 'actual_codex_cli_config_load' && !check.signals?.blockers?.length) {
|
|
160
|
+
if (!report.blockers.includes('codex_cli_config_load_unverified')) report.blockers.push('codex_cli_config_load_unverified');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function classifyText(textInput, opts = {}) {
|
|
165
|
+
const text = String(textInput || '');
|
|
166
|
+
const flags = {
|
|
167
|
+
error_loading_config_toml: /Error loading config\.toml/i.test(text),
|
|
168
|
+
operation_not_permitted: /Operation not permitted|os error 1|EPERM/i.test(text),
|
|
169
|
+
permission_denied: /Permission denied|EACCES/i.test(text),
|
|
170
|
+
toml_parse: /TOML parse|toml.*parse|parse.*toml|invalid.*toml|duplicate key/i.test(text),
|
|
171
|
+
// serde/toml deserialize failures from a structurally-broken config, e.g.
|
|
172
|
+
// `invalid type: sequence, expected a string` when `notify` was absorbed into
|
|
173
|
+
// an env table. These carry no literal "toml" token so the patterns above miss them.
|
|
174
|
+
toml_type_error: /invalid type:|invalid value:|expected (a |an )?(string|sequence|array|table|map|integer|boolean|float)|missing field|unknown field|unknown variant/i.test(text),
|
|
175
|
+
untrusted_project: /untrusted project/i.test(text),
|
|
176
|
+
ignored_project_local_config_key: /ignored project-local config key|ignored.*project.*config/i.test(text)
|
|
177
|
+
};
|
|
178
|
+
const blockers = [];
|
|
179
|
+
const warnings = [];
|
|
180
|
+
if (flags.operation_not_permitted || (flags.error_loading_config_toml && /os error 1/i.test(text))) blockers.push('codex_cli_config_eperm');
|
|
181
|
+
else if (flags.permission_denied) blockers.push('codex_cli_config_permission_denied');
|
|
182
|
+
// A config-load failure that is not a permission problem is a parse/structure error.
|
|
183
|
+
const tomlLoadFailure = flags.toml_parse
|
|
184
|
+
|| (!opts.configLoaded && flags.toml_type_error)
|
|
185
|
+
|| (!opts.configLoaded && flags.error_loading_config_toml && !flags.operation_not_permitted && !flags.permission_denied);
|
|
186
|
+
if (tomlLoadFailure) blockers.push('codex_cli_config_toml_parse_error');
|
|
187
|
+
if (flags.untrusted_project) blockers.push('codex_cli_untrusted_project');
|
|
188
|
+
if (flags.ignored_project_local_config_key) {
|
|
189
|
+
if (opts.configLoaded) warnings.push('codex_cli_ignored_project_local_config_key');
|
|
190
|
+
else blockers.push('codex_cli_ignored_project_local_config_key');
|
|
191
|
+
}
|
|
192
|
+
return { blockers: [...new Set(blockers)], warnings: [...new Set(warnings)], flags };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function commandForCodex(bin, codexArgs) {
|
|
196
|
+
if (/\.mjs$/i.test(String(bin))) return { command: process.execPath, args: [bin, ...codexArgs] };
|
|
197
|
+
return { command: bin, args: codexArgs };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function probeEnv(env) {
|
|
201
|
+
const keys = [
|
|
202
|
+
'CODEX_HOME',
|
|
203
|
+
'SKS_FAST_MODE',
|
|
204
|
+
'SKS_SERVICE_TIER',
|
|
205
|
+
'PATH',
|
|
206
|
+
'HOME',
|
|
207
|
+
'WARP_SESSION_ID',
|
|
208
|
+
'TMUX'
|
|
209
|
+
];
|
|
210
|
+
const out = {};
|
|
211
|
+
for (const key of keys) {
|
|
212
|
+
if (env[key] !== undefined) out[key] = env[key];
|
|
213
|
+
}
|
|
214
|
+
for (const [key, value] of Object.entries(env)) {
|
|
215
|
+
if (/^(CODEX_LB|SKS_CODEX_LB)_/.test(key)) out[key] = value;
|
|
216
|
+
if (/^SKS_FAKE_CODEX_CONFIG_/.test(key)) out[key] = value;
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function redact(value) {
|
|
222
|
+
return String(value || '')
|
|
223
|
+
.replace(/sk-[A-Za-z0-9_-]{8,}/g, '[REDACTED_OPENAI_KEY]')
|
|
224
|
+
.replace(/github_pat_[A-Za-z0-9_]+/g, '[REDACTED_GITHUB_PAT]')
|
|
225
|
+
.replace(/(CODEX_LB_API_KEY=)[^\s]+/g, '$1[REDACTED]')
|
|
226
|
+
.replace(/(OPENAI_API_KEY=)[^\s]+/g, '$1[REDACTED]');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function tail(value, limit = 4000) {
|
|
230
|
+
const text = redact(String(value || ''));
|
|
231
|
+
return text.length <= limit ? text : text.slice(-limit);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function readTextIfExists(file) {
|
|
235
|
+
try {
|
|
236
|
+
return await fs.readFile(file, 'utf8');
|
|
237
|
+
} catch {
|
|
238
|
+
return '';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readOption(name, fallback) {
|
|
243
|
+
const index = args.indexOf(name);
|
|
244
|
+
return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
|
|
245
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "1.18.
|
|
4
|
+
"version": "1.18.14",
|
|
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",
|