-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathcodex-config.js
More file actions
80 lines (72 loc) · 3 KB
/
codex-config.js
File metadata and controls
80 lines (72 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
function normalizeCodexToml(content) {
return String(content || '')
.replace(/\r\n/g, '\n')
.replace(/([^\n])(\[(?:projects|notice|plugins)\.[^\n]*\])/g, '$1\n$2')
.replace(/([^\n])(\[(?:features|otel)\])/g, '$1\n$2')
.replace(/([^\n])(\[\[skills\.[^\n]*\]\])/g, '$1\n$2');
}
function splitTomlSections(content) {
const lines = normalizeCodexToml(content).split('\n');
const top = [];
const sections = [];
let current = null;
for (const line of lines) {
if (/^\s*\[.*\]\s*$/.test(line)) {
current = { header: line.trim(), lines: [] };
sections.push(current);
continue;
}
if (current) current.lines.push(line);
else top.push(line);
}
return { top, sections };
}
function trimBlankEdges(lines) {
const out = [...lines];
while (out.length && !out[0].trim()) out.shift();
while (out.length && !out[out.length - 1].trim()) out.pop();
return out;
}
function upsertCodexConfig(content, nodePath, notifyHelperPath, port) {
const { top, sections } = splitTomlSections(content);
const notifyLine = `notify = ["${nodePath}", "${notifyHelperPath}", "${port}"]`;
const cleanedTop = top.filter(line => !/^\s*notify\s*=/.test(line));
const topOut = trimBlankEdges([...cleanedTop, notifyLine]);
sections.forEach(section => {
section.lines = section.lines.filter(line => !/^\s*notify\s*=/.test(line));
});
let hasFeatures = false;
const keptSections = sections.map(section => {
if (section.header !== '[features]') return section;
hasFeatures = true;
const lines = trimBlankEdges(section.lines.filter(line => !/^\s*(codex_hooks|hooks)\s*=/.test(line)));
return { ...section, lines: trimBlankEdges([...lines, 'hooks = true']) };
});
if (!hasFeatures) keptSections.push({ header: '[features]', lines: ['hooks = true'] });
const otelHeader = '[otel]';
const otelBody = [`exporter = { otlp-http = { endpoint = "http://localhost:${port}", protocol = "json" } }`];
const withoutOtel = keptSections.filter(s => s.header !== otelHeader);
withoutOtel.push({ header: otelHeader, lines: otelBody });
const out = [];
if (topOut.length) out.push(...topOut, '');
withoutOtel.forEach((section, idx) => {
out.push(section.header, ...trimBlankEdges(section.lines));
if (idx < withoutOtel.length - 1) out.push('');
});
return out.join('\n').trimEnd() + '\n';
}
function validateCodexConfigToml(content) {
const lines = normalizeCodexToml(content).split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const t = line.trim();
if (!t || t.startsWith('#')) continue;
if (/^\[\[.*\]\]$/.test(t) || /^\[.*\]$/.test(t)) continue;
if (!t.includes('=')) return { ok: false, error: `Invalid TOML line ${i + 1}: ${t}` };
if (/(?:^|[^\s=])\[(?:projects|notice|plugins)\.|(?:^|[^\s=])\[(?:features|otel)\]|(?:^|[^\s=])\[\[skills\./.test(t)) {
return { ok: false, error: `Glued TOML headers on line ${i + 1}` };
}
}
return { ok: true };
}
module.exports = { upsertCodexConfig, validateCodexConfigToml };