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/index.js ADDED
@@ -0,0 +1,759 @@
1
+ #!/usr/bin/env node
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ import { execSync } from 'node:child_process';
7
+ import { render, useInput, useApp, useStdout, Text, Box } from 'ink';
8
+ import { CONTROL_HINT, EDIT_CONTROL_HINT } from './src/constants.js';
9
+ import {
10
+ readConfig,
11
+ getNodeAtPath,
12
+ buildRows,
13
+ deleteValueAtPath,
14
+ setValueAtPath,
15
+ writeConfig,
16
+ } from './src/configParser.js';
17
+ import { getConfigOptions } from './src/configHelp.js';
18
+ import {
19
+ getReferenceOptionForPath,
20
+ getReferenceCustomIdPlaceholder,
21
+ } from './src/configReference.js';
22
+ import { pathToKey, clamp } from './src/layout.js';
23
+ import {
24
+ isBackspaceKey,
25
+ isDeleteKey,
26
+ isPageUpKey,
27
+ isPageDownKey,
28
+ isHomeKey,
29
+ isEndKey,
30
+ } from './src/interaction.js';
31
+ import { Header } from './src/components/Header.js';
32
+ import { ConfigNavigator } from './src/components/ConfigNavigator.js';
33
+
34
+ const computeListViewportHeight = (rows, terminalRows) =>
35
+ Math.max(4, Math.min(rows.length, Math.min(20, Math.max(4, terminalRows - 14))));
36
+
37
+ const isBooleanOnlyOptions = (options) =>
38
+ Array.isArray(options) &&
39
+ options.length === 2 &&
40
+ options.every((option) => typeof option === 'boolean') &&
41
+ options.includes(false) &&
42
+ options.includes(true);
43
+
44
+ const isStringReferenceType = (type) => /^string(?:\s|$)/.test(String(type || '').trim());
45
+
46
+ const isStringField = (pathSegments, value) => {
47
+ if (typeof value === 'string') {
48
+ return true;
49
+ }
50
+
51
+ return isStringReferenceType(getReferenceOptionForPath(pathSegments)?.type);
52
+ };
53
+
54
+ const isCustomIdTableRow = (pathSegments, row) =>
55
+ row?.kind === 'table' &&
56
+ typeof row?.pathSegment === 'string' &&
57
+ Boolean(getReferenceCustomIdPlaceholder(pathSegments));
58
+
59
+ const isInlineTextMode = (mode) => mode === 'text' || mode === 'add-id';
60
+
61
+ const normalizeCustomPathId = (value) => {
62
+ const trimmedValue = String(value || '').trim();
63
+ if (!trimmedValue) {
64
+ return '';
65
+ }
66
+
67
+ const withoutTilde = trimmedValue.startsWith('~')
68
+ ? trimmedValue.slice(1)
69
+ : trimmedValue;
70
+ const relativePath = withoutTilde.replace(/^[/\\]+/, '');
71
+
72
+ return path.join(os.homedir(), relativePath);
73
+ };
74
+
75
+ const getCodexVersion = () => {
76
+ try {
77
+ const output = execSync('codex --version', { encoding: 'utf8', stdio: 'pipe' }).trim();
78
+ const firstLine = output.split('\n')[0].trim();
79
+
80
+ if (!firstLine) {
81
+ return 'version unavailable';
82
+ }
83
+
84
+ return firstLine.startsWith('codex') ? firstLine : `version ${firstLine}`;
85
+ } catch {
86
+ return 'version unavailable';
87
+ }
88
+ };
89
+
90
+ const normalizeVersion = (value) => {
91
+ const match = String(value || '').match(/(\d+\.\d+\.\d+(?:[-+._][0-9A-Za-z.-]+)*)/);
92
+ return match ? match[1] : '';
93
+ };
94
+
95
+ const toVersionParts = (value) =>
96
+ normalizeVersion(value)
97
+ .split(/[-+._]/)[0]
98
+ .split('.')
99
+ .map((part) => Number.parseInt(part, 10))
100
+ .filter(Number.isFinite);
101
+
102
+ const compareVersions = (left, right) => {
103
+ const leftParts = toVersionParts(left);
104
+ const rightParts = toVersionParts(right);
105
+ const maxLength = Math.max(leftParts.length, rightParts.length);
106
+
107
+ for (let index = 0; index < maxLength; index += 1) {
108
+ const a = leftParts[index] || 0;
109
+ const b = rightParts[index] || 0;
110
+
111
+ if (a > b) {
112
+ return 1;
113
+ }
114
+
115
+ if (a < b) {
116
+ return -1;
117
+ }
118
+ }
119
+
120
+ return 0;
121
+ };
122
+
123
+ const getCodexUpdateStatus = () => {
124
+ const installed = normalizeVersion(getCodexVersion());
125
+
126
+ if (!installed) {
127
+ return {
128
+ installed: 'version unavailable',
129
+ latest: 'unknown',
130
+ status: 'version check unavailable',
131
+ };
132
+ }
133
+
134
+ try {
135
+ const latestOutput = execSync('npm view @openai/codex version --json', {
136
+ encoding: 'utf8',
137
+ stdio: 'pipe',
138
+ }).trim();
139
+ const latest = normalizeVersion(latestOutput) || latestOutput.trim();
140
+
141
+ if (!latest) {
142
+ return {
143
+ installed,
144
+ latest: 'unknown',
145
+ status: 'version check unavailable',
146
+ };
147
+ }
148
+
149
+ const comparison = compareVersions(installed, latest);
150
+ if (comparison < 0) {
151
+ return {
152
+ installed,
153
+ latest,
154
+ status: 'update available',
155
+ };
156
+ }
157
+
158
+ return {
159
+ installed,
160
+ latest,
161
+ status: 'up to date',
162
+ };
163
+ } catch {
164
+ return {
165
+ installed,
166
+ latest: 'unknown',
167
+ status: 'version check unavailable',
168
+ };
169
+ }
170
+ };
171
+
172
+ const App = () => {
173
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
174
+ const { stdout } = useStdout();
175
+ const terminalWidth = stdout?.columns || 100;
176
+ const terminalHeight = stdout?.rows || 24;
177
+
178
+ const [snapshot, setSnapshot] = useState(readConfig);
179
+ const [pathSegments, setPathSegments] = useState([]);
180
+ const [selectedIndex, setSelectedIndex] = useState(0);
181
+ const [selectionByPath, setSelectionByPath] = useState({});
182
+ const [scrollOffset, setScrollOffset] = useState(0);
183
+ const [editMode, setEditMode] = useState(null);
184
+ const [editError, setEditError] = useState('');
185
+ const [codexVersion, setCodexVersion] = useState('version loading...');
186
+ const [codexVersionStatus, setCodexVersionStatus] = useState('');
187
+ const { exit } = useApp();
188
+
189
+ useEffect(() => {
190
+ const check = getCodexUpdateStatus();
191
+ setCodexVersion(check.installed);
192
+ setCodexVersionStatus(check.status);
193
+ }, []);
194
+
195
+ const currentNode = getNodeAtPath(snapshot.ok ? snapshot.data : {}, pathSegments);
196
+ const rows = buildRows(currentNode, pathSegments);
197
+ const safeSelected = rows.length === 0 ? 0 : Math.min(selectedIndex, rows.length - 1);
198
+ const listViewportHeight = computeListViewportHeight(rows, terminalHeight);
199
+ const currentPathKey = pathToKey(pathSegments);
200
+
201
+ const getSavedIndex = (segments, fallback = 0) => {
202
+ const key = pathToKey(segments);
203
+ const maybe = selectionByPath[key];
204
+
205
+ if (Number.isInteger(maybe)) {
206
+ return maybe;
207
+ }
208
+
209
+ return fallback;
210
+ };
211
+
212
+ const adjustScrollForSelection = (nextSelection, nextViewportHeight, totalRows) => {
213
+ const maxOffset = Math.max(0, totalRows - nextViewportHeight);
214
+ const minOffset = 0;
215
+
216
+ setScrollOffset((previous) => {
217
+ if (nextSelection < previous) {
218
+ return clamp(nextSelection, minOffset, maxOffset);
219
+ }
220
+
221
+ if (nextSelection > previous + nextViewportHeight - 1) {
222
+ return clamp(nextSelection - nextViewportHeight + 1, minOffset, maxOffset);
223
+ }
224
+
225
+ return clamp(previous, minOffset, maxOffset);
226
+ });
227
+ };
228
+
229
+ const beginEditing = (target, targetPath) => {
230
+ const options = getConfigOptions(targetPath, target.key, target.value, target.kind) || [];
231
+ if (options.length === 0) {
232
+ return;
233
+ }
234
+
235
+ setEditError('');
236
+ setEditMode({
237
+ mode: 'select',
238
+ path: targetPath,
239
+ options,
240
+ selectedOptionIndex: clamp(options.findIndex((option) => Object.is(option, target.value)), 0, options.length - 1),
241
+ savedOptionIndex: null,
242
+ });
243
+ };
244
+
245
+ const beginTextEditing = (target, targetPath) => {
246
+ setEditError('');
247
+ setEditMode({
248
+ mode: 'text',
249
+ path: targetPath,
250
+ draftValue: typeof target.value === 'string' ? target.value : '',
251
+ savedValue: null,
252
+ });
253
+ };
254
+
255
+ const beginAddIdEditing = (targetPath, placeholder = 'id') => {
256
+ setEditError('');
257
+ setEditMode({
258
+ mode: 'add-id',
259
+ path: targetPath,
260
+ placeholder,
261
+ draftValue: '',
262
+ savedValue: null,
263
+ });
264
+ };
265
+
266
+ const applyEdit = () => {
267
+ if (!editMode || editMode.mode !== 'select') {
268
+ return;
269
+ }
270
+
271
+ const nextIndex = editMode.selectedOptionIndex;
272
+ const nextValue = editMode.options[nextIndex];
273
+ const nextData = setValueAtPath(snapshot.ok ? snapshot.data : {}, editMode.path, nextValue);
274
+ const writeResult = writeConfig(nextData, snapshot.path);
275
+
276
+ if (!writeResult.ok) {
277
+ setEditError(writeResult.error);
278
+ return;
279
+ }
280
+
281
+ setSnapshot({
282
+ ok: true,
283
+ path: snapshot.path,
284
+ data: nextData,
285
+ });
286
+ setEditMode(null);
287
+ setEditError('');
288
+ };
289
+
290
+ const applyTextEdit = () => {
291
+ if (!editMode || editMode.mode !== 'text') {
292
+ return;
293
+ }
294
+
295
+ const nextData = setValueAtPath(snapshot.ok ? snapshot.data : {}, editMode.path, editMode.draftValue);
296
+ const writeResult = writeConfig(nextData, snapshot.path);
297
+
298
+ if (!writeResult.ok) {
299
+ setEditError(writeResult.error);
300
+ return;
301
+ }
302
+
303
+ setSnapshot({
304
+ ok: true,
305
+ path: snapshot.path,
306
+ data: nextData,
307
+ });
308
+ setEditMode(null);
309
+ setEditError('');
310
+ };
311
+
312
+ const applyAddId = () => {
313
+ if (!editMode || editMode.mode !== 'add-id') {
314
+ return;
315
+ }
316
+
317
+ const nextIdInput = String(editMode.draftValue || '').trim();
318
+ const placeholder = getReferenceCustomIdPlaceholder(editMode.path);
319
+ const nextId = placeholder === '<path>'
320
+ ? normalizeCustomPathId(nextIdInput)
321
+ : nextIdInput;
322
+
323
+ if (!nextId) {
324
+ setEditError('ID cannot be empty.');
325
+ return;
326
+ }
327
+
328
+ const nextPath = [...editMode.path, nextId];
329
+ const data = snapshot.ok ? snapshot.data : {};
330
+ const existingValue = getNodeAtPath(data, nextPath);
331
+
332
+ if (typeof existingValue !== 'undefined') {
333
+ setEditError(`ID "${nextId}" already exists.`);
334
+ return;
335
+ }
336
+
337
+ const nextData = setValueAtPath(data, nextPath, {});
338
+ const writeResult = writeConfig(nextData, snapshot.path);
339
+
340
+ if (!writeResult.ok) {
341
+ setEditError(writeResult.error);
342
+ return;
343
+ }
344
+
345
+ setSnapshot({
346
+ ok: true,
347
+ path: snapshot.path,
348
+ data: nextData,
349
+ });
350
+ setPathSegments(nextPath);
351
+ setSelectedIndex(0);
352
+ setScrollOffset(0);
353
+ setEditMode(null);
354
+ setEditError('');
355
+ };
356
+
357
+ const applyBooleanToggle = (target, targetPath) => {
358
+ const nextValue = !target.value;
359
+ const data = snapshot.ok ? snapshot.data : {};
360
+ const nextData = setValueAtPath(data, targetPath, nextValue);
361
+
362
+ const writeResult = writeConfig(nextData, snapshot.path);
363
+
364
+ if (!writeResult.ok) {
365
+ setEditError(writeResult.error);
366
+ return;
367
+ }
368
+
369
+ setSnapshot({
370
+ ok: true,
371
+ path: snapshot.path,
372
+ data: nextData,
373
+ });
374
+ setEditError('');
375
+ };
376
+
377
+ const unsetValueAtPath = (targetPath) => {
378
+ const data = snapshot.ok ? snapshot.data : {};
379
+ const hasConfiguredValue = typeof getNodeAtPath(data, targetPath) !== 'undefined';
380
+
381
+ if (!hasConfiguredValue) {
382
+ setEditError('');
383
+ return;
384
+ }
385
+
386
+ const nextData = deleteValueAtPath(data, targetPath);
387
+ const writeResult = writeConfig(nextData, snapshot.path);
388
+
389
+ if (!writeResult.ok) {
390
+ setEditError(writeResult.error);
391
+ return;
392
+ }
393
+
394
+ setSnapshot({
395
+ ok: true,
396
+ path: snapshot.path,
397
+ data: nextData,
398
+ });
399
+ setEditError('');
400
+ };
401
+
402
+ useInput((input, key) => {
403
+ const isTextEditing = isInlineTextMode(editMode?.mode);
404
+
405
+ if (input === 'q' && !isTextEditing) {
406
+ exit();
407
+ return;
408
+ }
409
+
410
+ if (editMode) {
411
+ if (isInlineTextMode(editMode.mode)) {
412
+ if (key.return) {
413
+ if (editMode.mode === 'text') {
414
+ applyTextEdit();
415
+ } else {
416
+ applyAddId();
417
+ }
418
+ return;
419
+ }
420
+
421
+ if (key.escape) {
422
+ setEditMode(null);
423
+ setEditError('');
424
+ return;
425
+ }
426
+
427
+ if (key.leftArrow || isBackspaceKey(input, key)) {
428
+ setEditMode(null);
429
+ setEditError('');
430
+ return;
431
+ }
432
+
433
+ if (isDeleteKey(input, key)) {
434
+ setEditMode((previous) => ({
435
+ ...previous,
436
+ draftValue: previous.draftValue.slice(0, -1),
437
+ }));
438
+ return;
439
+ }
440
+
441
+ if (
442
+ key.rightArrow ||
443
+ key.upArrow ||
444
+ key.downArrow ||
445
+ isPageUpKey(input, key) ||
446
+ isPageDownKey(input, key) ||
447
+ isHomeKey(input, key) ||
448
+ isEndKey(input, key)
449
+ ) {
450
+ return;
451
+ }
452
+
453
+ if (!key.ctrl && !key.meta && input.length > 0) {
454
+ setEditMode((previous) => ({
455
+ ...previous,
456
+ draftValue: `${previous.draftValue}${input}`,
457
+ }));
458
+ }
459
+
460
+ return;
461
+ }
462
+
463
+ if (key.upArrow) {
464
+ setEditMode((previous) => ({
465
+ ...previous,
466
+ selectedOptionIndex: clamp(previous.selectedOptionIndex - 1, 0, previous.options.length - 1),
467
+ }));
468
+ return;
469
+ }
470
+
471
+ if (key.downArrow) {
472
+ setEditMode((previous) => ({
473
+ ...previous,
474
+ selectedOptionIndex: clamp(previous.selectedOptionIndex + 1, 0, previous.options.length - 1),
475
+ }));
476
+ return;
477
+ }
478
+
479
+ if (isPageUpKey(input, key)) {
480
+ setEditMode((previous) => ({
481
+ ...previous,
482
+ selectedOptionIndex: clamp(
483
+ previous.selectedOptionIndex - listViewportHeight,
484
+ 0,
485
+ previous.options.length - 1
486
+ ),
487
+ }));
488
+ return;
489
+ }
490
+
491
+ if (isPageDownKey(input, key)) {
492
+ setEditMode((previous) => ({
493
+ ...previous,
494
+ selectedOptionIndex: clamp(
495
+ previous.selectedOptionIndex + listViewportHeight,
496
+ 0,
497
+ previous.options.length - 1
498
+ ),
499
+ }));
500
+ return;
501
+ }
502
+
503
+ if (isHomeKey(input, key)) {
504
+ setEditMode((previous) => ({
505
+ ...previous,
506
+ selectedOptionIndex: 0,
507
+ }));
508
+ return;
509
+ }
510
+
511
+ if (isEndKey(input, key)) {
512
+ setEditMode((previous) => ({
513
+ ...previous,
514
+ selectedOptionIndex: Math.max(0, previous.options.length - 1),
515
+ }));
516
+ return;
517
+ }
518
+
519
+ if (key.return) {
520
+ applyEdit();
521
+ return;
522
+ }
523
+
524
+ if (key.leftArrow || isBackspaceKey(input, key)) {
525
+ setEditMode(null);
526
+ setEditError('');
527
+ return;
528
+ }
529
+
530
+ if (isDeleteKey(input, key)) {
531
+ return;
532
+ }
533
+
534
+ if (key.escape) {
535
+ setEditMode(null);
536
+ setEditError('');
537
+ return;
538
+ }
539
+ }
540
+
541
+ if (key.upArrow) {
542
+ if (rows.length === 0) {
543
+ return;
544
+ }
545
+
546
+ setSelectedIndex((previous) => {
547
+ const next = Math.max(previous - 1, 0);
548
+ adjustScrollForSelection(next, listViewportHeight, rows.length);
549
+ return next;
550
+ });
551
+ return;
552
+ }
553
+
554
+ if (key.downArrow) {
555
+ if (rows.length === 0) {
556
+ return;
557
+ }
558
+
559
+ setSelectedIndex((previous) => {
560
+ const next = Math.min(previous + 1, rows.length - 1);
561
+ adjustScrollForSelection(next, listViewportHeight, rows.length);
562
+ return next;
563
+ });
564
+ return;
565
+ }
566
+
567
+ if (isPageUpKey(input, key)) {
568
+ if (rows.length === 0) {
569
+ return;
570
+ }
571
+
572
+ setSelectedIndex((previous) => {
573
+ const next = Math.max(previous - listViewportHeight, 0);
574
+ adjustScrollForSelection(next, listViewportHeight, rows.length);
575
+ return next;
576
+ });
577
+ return;
578
+ }
579
+
580
+ if (isPageDownKey(input, key)) {
581
+ if (rows.length === 0) {
582
+ return;
583
+ }
584
+
585
+ setSelectedIndex((previous) => {
586
+ const next = Math.min(previous + listViewportHeight, rows.length - 1);
587
+ adjustScrollForSelection(next, listViewportHeight, rows.length);
588
+ return next;
589
+ });
590
+ return;
591
+ }
592
+
593
+ if (isHomeKey(input, key)) {
594
+ if (rows.length === 0) {
595
+ return;
596
+ }
597
+
598
+ setSelectedIndex(0);
599
+ adjustScrollForSelection(0, listViewportHeight, rows.length);
600
+ return;
601
+ }
602
+
603
+ if (isEndKey(input, key)) {
604
+ if (rows.length === 0) {
605
+ return;
606
+ }
607
+
608
+ const next = rows.length - 1;
609
+ setSelectedIndex(next);
610
+ adjustScrollForSelection(next, listViewportHeight, rows.length);
611
+ return;
612
+ }
613
+
614
+ if (key.return && rows[safeSelected]) {
615
+ const target = rows[safeSelected];
616
+
617
+ if (target.kind === 'action' && target.action === 'add-custom-id') {
618
+ beginAddIdEditing(pathSegments, target.placeholder || 'id');
619
+ return;
620
+ }
621
+
622
+ if (target.kind === 'table' || target.kind === 'tableArray') {
623
+ const nextPath = [...pathSegments, target.pathSegment];
624
+
625
+ setPathSegments((previous) => [...previous, target.pathSegment]);
626
+ setSelectionByPath((previous) => ({
627
+ ...previous,
628
+ [currentPathKey]: safeSelected,
629
+ }));
630
+
631
+ const nextNode = getNodeAtPath(snapshot.ok ? snapshot.data : {}, nextPath);
632
+ const nextRows = buildRows(nextNode, nextPath);
633
+ const nextViewportHeight = computeListViewportHeight(nextRows, terminalHeight);
634
+ const nextSavedIndex = getSavedIndex(nextPath, 0);
635
+ const nextSelected = nextRows.length === 0 ? 0 : clamp(nextSavedIndex, 0, nextRows.length - 1);
636
+
637
+ setSelectedIndex(nextSelected);
638
+ setScrollOffset(clamp(nextSelected, 0, Math.max(0, nextRows.length - nextViewportHeight)));
639
+ return;
640
+ }
641
+
642
+ const targetPath = [...pathSegments, target.pathSegment];
643
+ const options = getConfigOptions(targetPath, target.key, target.value, target.kind) || [];
644
+ if (typeof target.value === 'boolean' || isBooleanOnlyOptions(options)) {
645
+ applyBooleanToggle(
646
+ typeof target.value === 'boolean'
647
+ ? target
648
+ : { ...target, value: false },
649
+ targetPath
650
+ );
651
+ return;
652
+ }
653
+
654
+ if (options.length > 0) {
655
+ beginEditing(target, targetPath);
656
+ return;
657
+ }
658
+
659
+ if (isStringField(targetPath, target.value)) {
660
+ beginTextEditing(target, targetPath);
661
+ }
662
+ return;
663
+ }
664
+
665
+ if (isDeleteKey(input, key) && rows[safeSelected]) {
666
+ const target = rows[safeSelected];
667
+ const isValueRow = target.kind === 'value';
668
+ const isCustomIdRow = isCustomIdTableRow(pathSegments, target);
669
+ const isInsideArray = Array.isArray(currentNode);
670
+
671
+ if ((!isValueRow && !isCustomIdRow) || isInsideArray) {
672
+ return;
673
+ }
674
+
675
+ const targetPath = [...pathSegments, target.pathSegment];
676
+ unsetValueAtPath(targetPath);
677
+ return;
678
+ }
679
+
680
+ if (input === 'r') {
681
+ setSnapshot(readConfig());
682
+ setPathSegments([]);
683
+ setSelectedIndex(0);
684
+ setSelectionByPath({});
685
+ setScrollOffset(0);
686
+ setEditMode(null);
687
+ setEditError('');
688
+ return;
689
+ }
690
+
691
+ if (key.leftArrow || isBackspaceKey(input, key) || key.escape) {
692
+ if (pathSegments.length === 0) {
693
+ return;
694
+ }
695
+
696
+ const parentPath = pathSegments.slice(0, -1);
697
+ const parentNode = getNodeAtPath(snapshot.ok ? snapshot.data : {}, parentPath);
698
+ const parentRows = buildRows(parentNode, parentPath);
699
+ const savedIndex = getSavedIndex(parentPath, 0);
700
+
701
+ setPathSegments(parentPath);
702
+ setSelectionByPath((previous) => ({
703
+ ...previous,
704
+ [currentPathKey]: safeSelected,
705
+ }));
706
+
707
+ const parentViewportHeight = computeListViewportHeight(parentRows, terminalHeight);
708
+ const parentSelected = parentRows.length === 0 ? 0 : clamp(savedIndex, 0, parentRows.length - 1);
709
+ setSelectedIndex(parentSelected);
710
+ setScrollOffset(clamp(parentSelected, 0, Math.max(0, parentRows.length - parentViewportHeight)));
711
+ setEditMode(null);
712
+ setEditError('');
713
+ return;
714
+ }
715
+ });
716
+
717
+ useEffect(() => {
718
+ const maxOffset = Math.max(0, rows.length - listViewportHeight);
719
+ setScrollOffset((previous) => clamp(previous, 0, maxOffset));
720
+ }, [rows.length, listViewportHeight]);
721
+
722
+ if (!isInteractive) {
723
+ return React.createElement(
724
+ Box,
725
+ { flexDirection: 'column', padding: 1 },
726
+ React.createElement(Header, { codexVersion, codexVersionStatus }),
727
+ React.createElement(ConfigNavigator, {
728
+ snapshot,
729
+ pathSegments,
730
+ selectedIndex: 0,
731
+ terminalWidth,
732
+ terminalHeight,
733
+ scrollOffset: 0,
734
+ editMode: null,
735
+ editError: editError,
736
+ }),
737
+ React.createElement(Text, { color: 'yellow' }, 'Non-interactive mode: input is disabled.')
738
+ );
739
+ }
740
+
741
+ return React.createElement(
742
+ Box,
743
+ { flexDirection: 'column', padding: 1 },
744
+ React.createElement(Header, { codexVersion, codexVersionStatus }),
745
+ React.createElement(ConfigNavigator, {
746
+ snapshot,
747
+ pathSegments,
748
+ selectedIndex: safeSelected,
749
+ terminalWidth,
750
+ terminalHeight,
751
+ scrollOffset,
752
+ editMode,
753
+ editError,
754
+ }),
755
+ React.createElement(Text, { color: 'gray' }, editMode ? EDIT_CONTROL_HINT : CONTROL_HINT)
756
+ );
757
+ };
758
+
759
+ render(React.createElement(App));