codex-configurator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/index.js +759 -0
- package/package.json +42 -0
- package/src/components/ConfigNavigator.js +387 -0
- package/src/components/Header.js +33 -0
- package/src/configFeatures.js +233 -0
- package/src/configHelp.js +252 -0
- package/src/configParser.js +564 -0
- package/src/configReference.js +217 -0
- package/src/constants.js +4 -0
- package/src/interaction.js +25 -0
- package/src/layout.js +20 -0
- package/src/reference/config-reference.json +5837 -0
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-configurator",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TOML-aware Ink TUI for Codex Configurator",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"tui",
|
|
9
|
+
"terminal",
|
|
10
|
+
"cli",
|
|
11
|
+
"react",
|
|
12
|
+
"ink",
|
|
13
|
+
"node",
|
|
14
|
+
"configurator"
|
|
15
|
+
],
|
|
16
|
+
"author": "Codex Configurator",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"bin": {
|
|
19
|
+
"codex-configurator": "index.js"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node index.js",
|
|
23
|
+
"dev": "node index.js",
|
|
24
|
+
"lint": "node --check index.js && node --check src/configHelp.js src/configParser.js src/constants.js src/interaction.js src/layout.js src/components/Header.js src/components/ConfigNavigator.js",
|
|
25
|
+
"build": "node --check index.js && node --check src/configHelp.js src/configParser.js src/constants.js src/interaction.js src/layout.js src/components/Header.js src/components/ConfigNavigator.js",
|
|
26
|
+
"test": "npm run lint",
|
|
27
|
+
"prepack": "npm run test"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"src"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@iarna/toml": "^2.2.5",
|
|
38
|
+
"ink": "^6.2.1",
|
|
39
|
+
"react": "^19.0.0",
|
|
40
|
+
"toml": "^3.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
import {
|
|
4
|
+
getConfigHelp,
|
|
5
|
+
getConfigOptions,
|
|
6
|
+
getConfigOptionExplanation,
|
|
7
|
+
getConfigDefaultOption,
|
|
8
|
+
} from '../configHelp.js';
|
|
9
|
+
import { computePaneWidths, clamp } from '../layout.js';
|
|
10
|
+
import { getNodeAtPath, buildRows, formatDetails } from '../configParser.js';
|
|
11
|
+
|
|
12
|
+
const MenuItem = ({ isSelected, isDimmed, isDeprecated, label }) =>
|
|
13
|
+
React.createElement(
|
|
14
|
+
Text,
|
|
15
|
+
{
|
|
16
|
+
bold: isSelected,
|
|
17
|
+
color: isSelected ? 'yellow' : isDimmed ? 'gray' : 'white',
|
|
18
|
+
dimColor: !isSelected && isDimmed,
|
|
19
|
+
},
|
|
20
|
+
label,
|
|
21
|
+
isDeprecated ? React.createElement(Text, { color: 'yellow' }, ' [!]') : null
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const formatArrayItem = (value) => {
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
|
|
30
|
+
return String(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
return `[${value.length} item(s)]`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (Object.prototype.toString.call(value) === '[object Object]') {
|
|
38
|
+
const keys = Object.keys(value);
|
|
39
|
+
if (keys.length === 0) {
|
|
40
|
+
return '{}';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return `{${keys.join(', ')}}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return String(value);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const formatOptionValue = (value) => {
|
|
50
|
+
if (typeof value === 'string') {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof value === 'boolean' || value === null) {
|
|
55
|
+
return String(value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return String(value);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const truncate = (text, maxLength) =>
|
|
62
|
+
text.length <= maxLength ? text : `${text.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
63
|
+
|
|
64
|
+
const isBooleanOnlyOptions = (options) =>
|
|
65
|
+
Array.isArray(options) &&
|
|
66
|
+
options.length === 2 &&
|
|
67
|
+
options.every((option) => typeof option === 'boolean') &&
|
|
68
|
+
options.includes(false) &&
|
|
69
|
+
options.includes(true);
|
|
70
|
+
|
|
71
|
+
const renderArrayDetails = (rows) => {
|
|
72
|
+
const items = rows.slice(0, 5).map((item, index) =>
|
|
73
|
+
React.createElement(Text, { key: `array-item-${index}` }, ` ${index + 1}. ${formatArrayItem(item)}`)
|
|
74
|
+
);
|
|
75
|
+
const overflow = rows.length - items.length;
|
|
76
|
+
|
|
77
|
+
return React.createElement(
|
|
78
|
+
React.Fragment,
|
|
79
|
+
null,
|
|
80
|
+
...items,
|
|
81
|
+
overflow > 0 ? React.createElement(Text, { key: 'array-more' }, ` … and ${overflow} more`) : null
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const renderTextEditor = (draftValue) =>
|
|
86
|
+
React.createElement(
|
|
87
|
+
React.Fragment,
|
|
88
|
+
null,
|
|
89
|
+
React.createElement(Text, { color: 'white' }, `> ${draftValue}`),
|
|
90
|
+
React.createElement(Text, { color: 'gray' }, 'Type to edit • Enter: save • Esc: cancel')
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const renderIdEditor = (placeholder, draftValue) =>
|
|
94
|
+
React.createElement(
|
|
95
|
+
React.Fragment,
|
|
96
|
+
null,
|
|
97
|
+
React.createElement(Text, { color: 'white' }, `${placeholder || 'id'}: ${draftValue}`),
|
|
98
|
+
React.createElement(Text, { color: 'gray' }, 'Type id • Enter: create • Esc: cancel')
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const renderEditableOptions = (
|
|
102
|
+
options,
|
|
103
|
+
selectedOptionIndex,
|
|
104
|
+
defaultOptionIndex,
|
|
105
|
+
optionPathSegments,
|
|
106
|
+
rowKey,
|
|
107
|
+
savedOptionIndex = null,
|
|
108
|
+
showCursor = false
|
|
109
|
+
) => {
|
|
110
|
+
const optionRows = options.map((option, optionIndex) => {
|
|
111
|
+
const optionValueText = formatOptionValue(option);
|
|
112
|
+
const prefix = showCursor && optionIndex === selectedOptionIndex ? '▶ ' : ' ';
|
|
113
|
+
const valueText = `${prefix}${optionValueText}`;
|
|
114
|
+
const explanation = getConfigOptionExplanation(optionPathSegments, rowKey, option);
|
|
115
|
+
const isDefault = optionIndex === defaultOptionIndex;
|
|
116
|
+
const highlightDefault = selectedOptionIndex < 0 && isDefault;
|
|
117
|
+
return { optionIndex, valueText, explanation, optionValueText, isDefault, highlightDefault };
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const valueWidth = optionRows.reduce((max, item) => Math.max(max, item.valueText.length), 0);
|
|
121
|
+
|
|
122
|
+
return React.createElement(
|
|
123
|
+
React.Fragment,
|
|
124
|
+
null,
|
|
125
|
+
...optionRows.map(
|
|
126
|
+
({ optionIndex, valueText, explanation, optionValueText, isDefault, highlightDefault }) =>
|
|
127
|
+
React.createElement(
|
|
128
|
+
Box,
|
|
129
|
+
{
|
|
130
|
+
key: `option-${rowKey}-${optionIndex}`,
|
|
131
|
+
flexDirection: 'column',
|
|
132
|
+
},
|
|
133
|
+
React.createElement(
|
|
134
|
+
Box,
|
|
135
|
+
{
|
|
136
|
+
flexDirection: 'row',
|
|
137
|
+
},
|
|
138
|
+
React.createElement(
|
|
139
|
+
Text,
|
|
140
|
+
{
|
|
141
|
+
color: optionIndex === selectedOptionIndex
|
|
142
|
+
? 'yellow'
|
|
143
|
+
: highlightDefault
|
|
144
|
+
? 'whiteBright'
|
|
145
|
+
: 'white',
|
|
146
|
+
bold: optionIndex === selectedOptionIndex || highlightDefault,
|
|
147
|
+
},
|
|
148
|
+
`${valueText.padEnd(valueWidth, ' ')}`
|
|
149
|
+
),
|
|
150
|
+
explanation
|
|
151
|
+
? React.createElement(Text, { color: 'gray' }, ` — ${truncate(explanation, 100)}`)
|
|
152
|
+
: null,
|
|
153
|
+
isDefault
|
|
154
|
+
? React.createElement(Text, { color: 'cyan' }, ' [default]')
|
|
155
|
+
: null
|
|
156
|
+
),
|
|
157
|
+
savedOptionIndex === optionIndex
|
|
158
|
+
? React.createElement(Text, { color: 'green' }, ` Saved: ${optionValueText}`)
|
|
159
|
+
: null
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const formatConfigHelp = (pathSegments, row) => {
|
|
166
|
+
if (row?.kind === 'action' && row?.action === 'add-custom-id') {
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
text: `Create a new custom "${row.placeholder || 'id'}" entry in this section.`,
|
|
170
|
+
color: 'white',
|
|
171
|
+
bold: false,
|
|
172
|
+
showWarningIcon: false,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
text: 'Press Enter to type the id, then Enter again to save.',
|
|
176
|
+
color: 'gray',
|
|
177
|
+
bold: false,
|
|
178
|
+
showWarningIcon: false,
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const info = getConfigHelp(pathSegments, row.key);
|
|
184
|
+
const defaultCollectionText =
|
|
185
|
+
row.kind === 'table' || row.kind === 'tableArray'
|
|
186
|
+
? 'This section groups related settings.'
|
|
187
|
+
: row.kind === 'array'
|
|
188
|
+
? `This is a list with ${row.value.length} entries.`
|
|
189
|
+
: 'This setting affects Codex behavior.';
|
|
190
|
+
const short = info?.short || defaultCollectionText;
|
|
191
|
+
const usage = info?.usage;
|
|
192
|
+
const isWarning = Boolean(info?.deprecation);
|
|
193
|
+
const lines = [{ text: short, color: 'white', bold: false, showWarningIcon: false }];
|
|
194
|
+
|
|
195
|
+
if (usage) {
|
|
196
|
+
lines.push({
|
|
197
|
+
text: isWarning ? usage.replace(/^\[!\]\s*/, '') : usage,
|
|
198
|
+
color: 'gray',
|
|
199
|
+
bold: false,
|
|
200
|
+
showWarningIcon: isWarning,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (row?.key === 'approval_policy' && row?.value === 'on-failure') {
|
|
205
|
+
lines.push({
|
|
206
|
+
text: 'approval_policy = "on-failure" is deprecated; use "untrusted", "on-request", or "never".',
|
|
207
|
+
color: 'gray',
|
|
208
|
+
bold: false,
|
|
209
|
+
showWarningIcon: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (pathSegments?.[pathSegments.length - 1] === 'features' && row?.isDocumented === false) {
|
|
214
|
+
lines.push({
|
|
215
|
+
text: 'This key is configured in your file but is not in the official feature list.',
|
|
216
|
+
color: 'gray',
|
|
217
|
+
bold: false,
|
|
218
|
+
showWarningIcon: true,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (pathSegments.length === 0 && row?.key === 'model') {
|
|
223
|
+
lines.push({
|
|
224
|
+
text: 'Model values shown here are curated presets and not a full upstream model catalog.',
|
|
225
|
+
color: 'gray',
|
|
226
|
+
bold: false,
|
|
227
|
+
showWarningIcon: false,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return lines;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const ConfigNavigator = ({
|
|
235
|
+
snapshot,
|
|
236
|
+
pathSegments,
|
|
237
|
+
selectedIndex,
|
|
238
|
+
scrollOffset,
|
|
239
|
+
terminalWidth,
|
|
240
|
+
terminalHeight,
|
|
241
|
+
editMode,
|
|
242
|
+
editError,
|
|
243
|
+
}) => {
|
|
244
|
+
if (!snapshot.ok) {
|
|
245
|
+
return React.createElement(
|
|
246
|
+
Box,
|
|
247
|
+
{ flexDirection: 'column', marginTop: 1 },
|
|
248
|
+
React.createElement(Text, { color: 'red', bold: true }, 'Unable to read/parse config'),
|
|
249
|
+
React.createElement(Text, { color: 'gray' }, snapshot.error),
|
|
250
|
+
React.createElement(Text, { color: 'gray' }, `Path: ${snapshot.path}`)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const currentNode = getNodeAtPath(snapshot.data, pathSegments);
|
|
255
|
+
const rows = buildRows(currentNode, pathSegments);
|
|
256
|
+
const selected = rows.length === 0 ? 0 : Math.min(selectedIndex, rows.length - 1);
|
|
257
|
+
const { leftWidth, rightWidth } = computePaneWidths(terminalWidth, rows);
|
|
258
|
+
const terminalListHeight = Math.max(4, (terminalHeight || 24) - 14);
|
|
259
|
+
const viewportHeight = Math.max(4, Math.min(rows.length, Math.min(20, terminalListHeight)));
|
|
260
|
+
const viewportStart = clamp(scrollOffset, 0, Math.max(0, rows.length - viewportHeight));
|
|
261
|
+
const visibleRows = rows.slice(viewportStart, viewportStart + viewportHeight);
|
|
262
|
+
const canScrollUp = viewportStart > 0;
|
|
263
|
+
const canScrollDown = viewportStart + viewportHeight < rows.length;
|
|
264
|
+
const selectedRow = rows[selected] || null;
|
|
265
|
+
const selectedPath =
|
|
266
|
+
selectedRow && selectedRow.pathSegment != null
|
|
267
|
+
? [...pathSegments, selectedRow.pathSegment]
|
|
268
|
+
: pathSegments;
|
|
269
|
+
const readOnlyOptions =
|
|
270
|
+
selectedRow && selectedRow.kind === 'value'
|
|
271
|
+
? getConfigOptions(selectedPath, selectedRow.key, selectedRow.value, selectedRow.kind) || []
|
|
272
|
+
: [];
|
|
273
|
+
const readOnlyOptionIndex = readOnlyOptions.findIndex((option) => Object.is(option, selectedRow?.value));
|
|
274
|
+
const readOnlyDefaultOption = selectedRow
|
|
275
|
+
? getConfigDefaultOption(selectedPath, selectedRow.key, selectedRow.kind, readOnlyOptions)
|
|
276
|
+
: null;
|
|
277
|
+
const readOnlyDefaultOptionIndex = readOnlyOptions.findIndex((option) =>
|
|
278
|
+
Object.is(option, readOnlyDefaultOption)
|
|
279
|
+
);
|
|
280
|
+
const shouldShowReadOnlyOptions =
|
|
281
|
+
readOnlyOptions.length > 0 &&
|
|
282
|
+
!isBooleanOnlyOptions(readOnlyOptions);
|
|
283
|
+
|
|
284
|
+
const editRow = rows[selected] || null;
|
|
285
|
+
const editDefaultOption = editMode && editMode.mode === 'select' && editRow
|
|
286
|
+
? getConfigDefaultOption(editMode.path, editRow.key, 'value', editMode.options)
|
|
287
|
+
: null;
|
|
288
|
+
const editDefaultOptionIndex = editMode && editMode.mode === 'select'
|
|
289
|
+
? editMode.options.findIndex((option) => Object.is(option, editDefaultOption))
|
|
290
|
+
: -1;
|
|
291
|
+
|
|
292
|
+
return React.createElement(
|
|
293
|
+
Box,
|
|
294
|
+
{ flexDirection: 'row', gap: 2 },
|
|
295
|
+
React.createElement(
|
|
296
|
+
Box,
|
|
297
|
+
{ flexDirection: 'column', width: leftWidth },
|
|
298
|
+
React.createElement(
|
|
299
|
+
Box,
|
|
300
|
+
{
|
|
301
|
+
flexDirection: 'column',
|
|
302
|
+
borderStyle: 'single',
|
|
303
|
+
borderColor: 'gray',
|
|
304
|
+
padding: 1,
|
|
305
|
+
},
|
|
306
|
+
rows.length === 0
|
|
307
|
+
? React.createElement(Text, { color: 'gray' }, '[no entries in this table]')
|
|
308
|
+
: visibleRows.map((row, viewIndex) => {
|
|
309
|
+
const index = viewportStart + viewIndex;
|
|
310
|
+
const showTopCue = canScrollUp && viewIndex === 0;
|
|
311
|
+
const showBottomCue = canScrollDown && viewIndex === visibleRows.length - 1;
|
|
312
|
+
const isSelected = index === selected;
|
|
313
|
+
const label = `${showTopCue ? '↑ ' : showBottomCue ? '↓ ' : ' '}${isSelected ? '▶' : ' '} ${row.label}`;
|
|
314
|
+
|
|
315
|
+
return React.createElement(
|
|
316
|
+
MenuItem,
|
|
317
|
+
{
|
|
318
|
+
label,
|
|
319
|
+
isSelected,
|
|
320
|
+
isDimmed: !isSelected && row.isConfigured === false,
|
|
321
|
+
isDeprecated: row.isDeprecated,
|
|
322
|
+
key: `left-${index}`,
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
})
|
|
326
|
+
)
|
|
327
|
+
),
|
|
328
|
+
React.createElement(
|
|
329
|
+
Box,
|
|
330
|
+
{ flexDirection: 'column', width: rightWidth, marginTop: 2 },
|
|
331
|
+
rows.length === 0
|
|
332
|
+
? React.createElement(Text, { color: 'gray' }, 'No selection available.')
|
|
333
|
+
: React.createElement(
|
|
334
|
+
React.Fragment,
|
|
335
|
+
null,
|
|
336
|
+
...formatConfigHelp(pathSegments, rows[selected]).map((line, lineIndex) =>
|
|
337
|
+
React.createElement(
|
|
338
|
+
Text,
|
|
339
|
+
{
|
|
340
|
+
key: `help-${selected}-${lineIndex}`,
|
|
341
|
+
color: line.color,
|
|
342
|
+
bold: line.bold,
|
|
343
|
+
},
|
|
344
|
+
line.showWarningIcon
|
|
345
|
+
? React.createElement(Text, { color: 'yellow' }, '[!] ')
|
|
346
|
+
: null,
|
|
347
|
+
line.text
|
|
348
|
+
)
|
|
349
|
+
),
|
|
350
|
+
editError ? React.createElement(Text, { color: 'red' }, editError) : null,
|
|
351
|
+
editMode ? React.createElement(Text, { color: 'gray' }, ' ') : null,
|
|
352
|
+
editMode
|
|
353
|
+
? editMode.mode === 'text'
|
|
354
|
+
? renderTextEditor(editMode.draftValue)
|
|
355
|
+
: editMode.mode === 'add-id'
|
|
356
|
+
? renderIdEditor(editMode.placeholder, editMode.draftValue)
|
|
357
|
+
: renderEditableOptions(
|
|
358
|
+
editMode.options,
|
|
359
|
+
editMode.selectedOptionIndex,
|
|
360
|
+
editDefaultOptionIndex,
|
|
361
|
+
editMode.path.slice(0, -1),
|
|
362
|
+
rows[selected].key,
|
|
363
|
+
editMode.savedOptionIndex,
|
|
364
|
+
true
|
|
365
|
+
)
|
|
366
|
+
: shouldShowReadOnlyOptions
|
|
367
|
+
? React.createElement(
|
|
368
|
+
React.Fragment,
|
|
369
|
+
null,
|
|
370
|
+
React.createElement(Text, { color: 'gray' }, ' '),
|
|
371
|
+
renderEditableOptions(
|
|
372
|
+
readOnlyOptions,
|
|
373
|
+
readOnlyOptionIndex,
|
|
374
|
+
readOnlyDefaultOptionIndex,
|
|
375
|
+
selectedPath,
|
|
376
|
+
selectedRow?.key,
|
|
377
|
+
null,
|
|
378
|
+
false
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
: rows[selected].kind === 'array'
|
|
382
|
+
? renderArrayDetails(rows[selected].value)
|
|
383
|
+
: null
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
|
|
4
|
+
const WORDMARK = [
|
|
5
|
+
' ██████╗ ██████╗ ██████╗ ███████╗██╗ ██╗ ██████╗ ██████╗ ███╗ ██╗███████╗██╗ ██████╗ ██╗ ██╗██████╗ █████╗ ████████╗ ██████╗ ██████╗ ',
|
|
6
|
+
'██╔════╝██╔═══██╗██╔══██╗██╔════╝╚██╗██╔╝ ██╔════╝██╔═══██╗████╗ ██║██╔════╝██║██╔════╝ ██║ ██║██╔══██╗██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗',
|
|
7
|
+
'██║ ██║ ██║██║ ██║█████╗ ╚███╔╝ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║██║ ███╗██║ ██║██████╔╝███████║ ██║ ██║ ██║██████╔╝',
|
|
8
|
+
'██║ ██║ ██║██║ ██║██╔══╝ ██╔██╗ ██║ ██║ ██║██║╚██╗██║██╔══╝ ██║██║ ██║██║ ██║██╔══██╗██╔══██║ ██║ ██║ ██║██╔══██╗',
|
|
9
|
+
'╚██████╗╚██████╔╝██████╔╝███████╗██╔╝ ██╗ ╚██████╗╚██████╔╝██║ ╚████║██║ ██║╚██████╔╝╚██████╔╝██║ ██║██║ ██║ ██║ ╚██████╔╝██║ ██║',
|
|
10
|
+
' ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const Header = ({ codexVersion, codexVersionStatus }) =>
|
|
14
|
+
React.createElement(
|
|
15
|
+
Box,
|
|
16
|
+
{
|
|
17
|
+
flexDirection: 'column',
|
|
18
|
+
paddingX: 1,
|
|
19
|
+
marginBottom: 1,
|
|
20
|
+
},
|
|
21
|
+
React.createElement(
|
|
22
|
+
Box,
|
|
23
|
+
{ flexDirection: 'column', marginBottom: 1, gap: 0 },
|
|
24
|
+
...WORDMARK.map((line) =>
|
|
25
|
+
React.createElement(Text, { color: 'magentaBright', bold: true, key: `word-${line}` }, line)
|
|
26
|
+
)
|
|
27
|
+
),
|
|
28
|
+
React.createElement(
|
|
29
|
+
Text,
|
|
30
|
+
{ color: 'gray' },
|
|
31
|
+
codexVersionStatus ? `Codex ${codexVersion} (${codexVersionStatus})` : `Codex ${codexVersion}`
|
|
32
|
+
)
|
|
33
|
+
);
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { getReferenceFeatureKeys } from './configReference.js';
|
|
2
|
+
|
|
3
|
+
const prettifyFeatureName = (key) =>
|
|
4
|
+
key
|
|
5
|
+
.split('_')
|
|
6
|
+
.map((segment) => `${segment[0]?.toUpperCase() || ''}${segment.slice(1)}`)
|
|
7
|
+
.join(' ');
|
|
8
|
+
|
|
9
|
+
const makeFeatureDefinition = (
|
|
10
|
+
key,
|
|
11
|
+
short,
|
|
12
|
+
usage = 'Uses the feature flag value in your config.',
|
|
13
|
+
options = {}
|
|
14
|
+
) => {
|
|
15
|
+
const deprecation = options?.deprecation;
|
|
16
|
+
const defaultValue = typeof options?.defaultValue === 'boolean' ? options.defaultValue : false;
|
|
17
|
+
const isDocumented = options?.isDocumented === true;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
key,
|
|
21
|
+
short,
|
|
22
|
+
usage: deprecation ? `[!] ${deprecation}` : usage,
|
|
23
|
+
deprecation,
|
|
24
|
+
defaultValue,
|
|
25
|
+
isDocumented,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const CONFIG_FEATURE_DEFINITIONS = [
|
|
30
|
+
makeFeatureDefinition(
|
|
31
|
+
'apply_patch_freeform',
|
|
32
|
+
'Enable freeform apply_patch style for code edits.',
|
|
33
|
+
'When enabled, patch edits can use larger freeform blocks.',
|
|
34
|
+
{ isDocumented: true }
|
|
35
|
+
),
|
|
36
|
+
makeFeatureDefinition(
|
|
37
|
+
'apps',
|
|
38
|
+
'Enable built-in app/connector features.',
|
|
39
|
+
'Toggle access to app integrations used by Codex.',
|
|
40
|
+
{ isDocumented: true }
|
|
41
|
+
),
|
|
42
|
+
makeFeatureDefinition(
|
|
43
|
+
'apps_mcp_gateway',
|
|
44
|
+
'Use the MCP app gateway.',
|
|
45
|
+
'Route app calls through the MCP gateway when available.',
|
|
46
|
+
{ isDocumented: true }
|
|
47
|
+
),
|
|
48
|
+
makeFeatureDefinition(
|
|
49
|
+
'child_agents_md',
|
|
50
|
+
'Allow child-agent markdown workflows.',
|
|
51
|
+
'Enable loading child-agent instructions from markdown files.',
|
|
52
|
+
{ isDocumented: true }
|
|
53
|
+
),
|
|
54
|
+
makeFeatureDefinition('codex_git_commit', 'Enable Codex-assisted git commit workflows.', 'Allows additional git-oriented agent workflows.'),
|
|
55
|
+
makeFeatureDefinition(
|
|
56
|
+
'collab',
|
|
57
|
+
'Enable collaboration mode helpers.',
|
|
58
|
+
'Turn on multi-party collaboration flow features.',
|
|
59
|
+
{ deprecation: 'collab is deprecated; use [features].multi_agent instead.' }
|
|
60
|
+
),
|
|
61
|
+
makeFeatureDefinition(
|
|
62
|
+
'collaboration_modes',
|
|
63
|
+
'Enable multiple collaboration modes.',
|
|
64
|
+
'Allow the assistant to switch between interaction modes.',
|
|
65
|
+
{ defaultValue: true, isDocumented: true }
|
|
66
|
+
),
|
|
67
|
+
makeFeatureDefinition('connectors', 'Enable external connector support.', 'Controls optional third-party connector behavior.'),
|
|
68
|
+
makeFeatureDefinition(
|
|
69
|
+
'elevated_windows_sandbox',
|
|
70
|
+
'Enable elevated Windows sandbox.',
|
|
71
|
+
'Allows higher-permission sandbox behavior on Windows.',
|
|
72
|
+
{ isDocumented: true }
|
|
73
|
+
),
|
|
74
|
+
makeFeatureDefinition('enable_experimental_windows_sandbox', 'Enable experimental Windows sandbox.', 'Turns on experimental Windows sandbox behavior.'),
|
|
75
|
+
makeFeatureDefinition('enable_request_compression', 'Enable request compression.', 'Compress request payloads for matching model endpoints.'),
|
|
76
|
+
makeFeatureDefinition('experimental_use_freeform_apply_patch', 'Enable experimental freeform apply_patch usage.', 'Allows experimental patching behavior in edit flows.'),
|
|
77
|
+
makeFeatureDefinition('experimental_use_unified_exec_tool', 'Enable experimental unified exec tool.', 'Use unified execution routing when invoking shell tools.'),
|
|
78
|
+
makeFeatureDefinition(
|
|
79
|
+
'experimental_windows_sandbox',
|
|
80
|
+
'Enable experimental Windows sandbox.',
|
|
81
|
+
'Turns on a newer Windows sandbox implementation.',
|
|
82
|
+
{ isDocumented: true }
|
|
83
|
+
),
|
|
84
|
+
makeFeatureDefinition('include_apply_patch_tool', 'Expose the apply_patch tool.', 'Makes apply patch tool calls available to the model.'),
|
|
85
|
+
makeFeatureDefinition('js_repl', 'Enable JavaScript REPL.', 'Allow JavaScript-based REPL style tooling.'),
|
|
86
|
+
makeFeatureDefinition('js_repl_tools_only', 'Restrict js_repl to tool-only mode.', 'Limits js_repl usage to explicit tool calls.'),
|
|
87
|
+
makeFeatureDefinition('memory_tool', 'Enable memory tool support.', 'Allows Codex to use memory-related workflow tools.'),
|
|
88
|
+
makeFeatureDefinition(
|
|
89
|
+
'multi_agent',
|
|
90
|
+
'Enable multi-agent support.',
|
|
91
|
+
'Allows multiple coordinated agent contexts.',
|
|
92
|
+
{ isDocumented: true }
|
|
93
|
+
),
|
|
94
|
+
makeFeatureDefinition(
|
|
95
|
+
'personality',
|
|
96
|
+
'Enable personality controls.',
|
|
97
|
+
'Turns on personality-related routing flags.',
|
|
98
|
+
{ defaultValue: true, isDocumented: true }
|
|
99
|
+
),
|
|
100
|
+
makeFeatureDefinition(
|
|
101
|
+
'powershell_utf8',
|
|
102
|
+
'Use UTF-8 in PowerShell sessions.',
|
|
103
|
+
'Keep PowerShell command output in UTF-8 encoding.',
|
|
104
|
+
{ defaultValue: true, isDocumented: true }
|
|
105
|
+
),
|
|
106
|
+
makeFeatureDefinition('prevent_idle_sleep', 'Prevent idle sleep while running.', 'Keeps the system awake during active sessions.'),
|
|
107
|
+
makeFeatureDefinition(
|
|
108
|
+
'remote_models',
|
|
109
|
+
'Enable remote model discovery.',
|
|
110
|
+
'Allows loading model lists from remote model providers.',
|
|
111
|
+
{ isDocumented: true }
|
|
112
|
+
),
|
|
113
|
+
makeFeatureDefinition(
|
|
114
|
+
'request_rule',
|
|
115
|
+
'Enable request rule controls.',
|
|
116
|
+
'Turn on request routing/validation policy behavior.',
|
|
117
|
+
{ defaultValue: true, isDocumented: true }
|
|
118
|
+
),
|
|
119
|
+
makeFeatureDefinition('responses_websockets', 'Enable Responses WebSocket transport.', 'Use websocket transport for Responses API calls.'),
|
|
120
|
+
makeFeatureDefinition('responses_websockets_v2', 'Enable Responses WebSocket v2 transport.', 'Use the next-generation websocket transport stack.'),
|
|
121
|
+
makeFeatureDefinition(
|
|
122
|
+
'runtime_metrics',
|
|
123
|
+
'Enable runtime metrics collection.',
|
|
124
|
+
'Record runtime metrics for analysis and telemetry.',
|
|
125
|
+
{ isDocumented: true }
|
|
126
|
+
),
|
|
127
|
+
makeFeatureDefinition(
|
|
128
|
+
'search_tool',
|
|
129
|
+
'Enable internal search tool.',
|
|
130
|
+
'Turns on the built-in search execution tool.',
|
|
131
|
+
{ isDocumented: true }
|
|
132
|
+
),
|
|
133
|
+
makeFeatureDefinition(
|
|
134
|
+
'shell_snapshot',
|
|
135
|
+
'Enable shell snapshots.',
|
|
136
|
+
'Capture shell environment state before runs.',
|
|
137
|
+
{ isDocumented: true }
|
|
138
|
+
),
|
|
139
|
+
makeFeatureDefinition(
|
|
140
|
+
'shell_tool',
|
|
141
|
+
'Enable shell tool access.',
|
|
142
|
+
'Allows Codex to run shell commands through the tool interface.',
|
|
143
|
+
{ defaultValue: true, isDocumented: true }
|
|
144
|
+
),
|
|
145
|
+
makeFeatureDefinition('shell_zsh_fork', 'Enable zsh forked shell tool.', 'Runs shell commands through a forked zsh process.'),
|
|
146
|
+
makeFeatureDefinition('skill_env_var_dependency_prompt', 'Enable environment variable prompts for skills.', 'Prompts when skills require missing environment variables.'),
|
|
147
|
+
makeFeatureDefinition('skill_mcp_dependency_install', 'Enable auto install MCP skill dependencies.', 'Installs missing MCP dependencies for skill execution.'),
|
|
148
|
+
makeFeatureDefinition('sqlite', 'Enable SQLite support features.', 'Turns SQLite-specific feature behavior on or off.'),
|
|
149
|
+
makeFeatureDefinition('steer', 'Enable steering control mode.', 'Allows steering the tool call strategy for some actions.'),
|
|
150
|
+
makeFeatureDefinition('undo', 'Enable undo behavior.', 'Expose undo controls for reversible operations.'),
|
|
151
|
+
makeFeatureDefinition(
|
|
152
|
+
'unified_exec',
|
|
153
|
+
'Use unified execution layer.',
|
|
154
|
+
'Route execution commands through the unified tool layer.',
|
|
155
|
+
{ isDocumented: true }
|
|
156
|
+
),
|
|
157
|
+
makeFeatureDefinition(
|
|
158
|
+
'use_linux_sandbox_bwrap',
|
|
159
|
+
'Enable Linux bubblewrap sandbox.',
|
|
160
|
+
'Use bwrap for Linux sandbox isolation.',
|
|
161
|
+
{ isDocumented: true }
|
|
162
|
+
),
|
|
163
|
+
makeFeatureDefinition(
|
|
164
|
+
'web_search',
|
|
165
|
+
'Enable web search tool.',
|
|
166
|
+
'Allows the model to call a web search tool.',
|
|
167
|
+
{
|
|
168
|
+
deprecation: '[features].web_search is deprecated; use the top-level web_search setting instead.',
|
|
169
|
+
isDocumented: true,
|
|
170
|
+
}
|
|
171
|
+
),
|
|
172
|
+
makeFeatureDefinition(
|
|
173
|
+
'web_search_cached',
|
|
174
|
+
'Enable cached web search only.',
|
|
175
|
+
'Restricts web search calls to cached results.',
|
|
176
|
+
{
|
|
177
|
+
deprecation:
|
|
178
|
+
'[features].web_search_cached is deprecated legacy toggle. `true` maps to `web_search = "cached"`.',
|
|
179
|
+
isDocumented: true,
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
makeFeatureDefinition(
|
|
183
|
+
'web_search_request',
|
|
184
|
+
'Enable web search request flow.',
|
|
185
|
+
'Turns on request-mode web search behavior.',
|
|
186
|
+
{
|
|
187
|
+
deprecation:
|
|
188
|
+
'[features].web_search_request is deprecated legacy toggle. `true` maps to `web_search = "live"`.',
|
|
189
|
+
isDocumented: true,
|
|
190
|
+
}
|
|
191
|
+
),
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const FEATURE_DEFINITION_MAP = CONFIG_FEATURE_DEFINITIONS.reduce((acc, definition) => {
|
|
195
|
+
acc[definition.key] = definition;
|
|
196
|
+
|
|
197
|
+
return acc;
|
|
198
|
+
}, {});
|
|
199
|
+
|
|
200
|
+
const DOCUMENTED_REFERENCE_FEATURE_KEYS = getReferenceFeatureKeys();
|
|
201
|
+
|
|
202
|
+
export const getConfigFeatureKeys = () => {
|
|
203
|
+
if (DOCUMENTED_REFERENCE_FEATURE_KEYS.length > 0) {
|
|
204
|
+
return DOCUMENTED_REFERENCE_FEATURE_KEYS;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return CONFIG_FEATURE_DEFINITIONS
|
|
208
|
+
.filter((definition) => definition.isDocumented === true)
|
|
209
|
+
.map((definition) => definition.key);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const getConfigFeatureDefinition = (key) => FEATURE_DEFINITION_MAP[key];
|
|
213
|
+
|
|
214
|
+
export const getConfigFeatureDefinitionOrFallback = (key) => {
|
|
215
|
+
if (!key) {
|
|
216
|
+
return {
|
|
217
|
+
short: `${prettifyFeatureName(String(key))}`,
|
|
218
|
+
usage: 'Uses a supported feature flag in your Codex config.',
|
|
219
|
+
defaultValue: false,
|
|
220
|
+
isDocumented: false,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
FEATURE_DEFINITION_MAP[key] || {
|
|
226
|
+
key,
|
|
227
|
+
short: prettifyFeatureName(String(key)),
|
|
228
|
+
usage: 'This configured key is not in the official feature list.',
|
|
229
|
+
defaultValue: false,
|
|
230
|
+
isDocumented: false,
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
};
|