codex-configurator 0.2.4 → 0.2.6
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 +45 -17
- package/index.js +1467 -839
- package/package.json +4 -2
- package/src/appState.js +80 -0
- package/src/components/ConfigNavigator.js +633 -430
- package/src/components/Header.js +26 -50
- package/src/configFeatures.js +39 -175
- package/src/configHelp.js +20 -236
- package/src/configParser.js +117 -32
- package/src/configReference.js +1002 -35
- package/src/constants.js +10 -3
- package/src/fileContext.js +118 -0
- package/src/interaction.js +69 -25
- package/src/layout.js +80 -15
- package/src/reference/config-schema.json +2120 -0
- package/src/schemaRuntimeSync.js +282 -0
- package/src/ui/commands.js +699 -0
- package/src/ui/panes/CommandBar.js +90 -0
- package/src/ui/panes/HelpBubble.js +26 -0
- package/src/ui/panes/LayoutShell.js +17 -0
- package/src/ui/panes/StatusLine.js +123 -0
- package/src/variantPresets.js +268 -0
- package/src/reference/config-reference.json +0 -3494
package/index.js
CHANGED
|
@@ -1,66 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { useEffect, useReducer, useRef, useState } from 'react';
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
6
8
|
import { render, useInput, useApp, useStdout, Text, Box } from 'ink';
|
|
7
|
-
import { CONTROL_HINT, EDIT_CONTROL_HINT, FILTER_CONTROL_HINT } from './src/constants.js';
|
|
8
9
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
buildConfigFileCatalog,
|
|
11
|
+
MAIN_CONFIG_FILE_ID,
|
|
12
|
+
} from './src/fileContext.js';
|
|
13
|
+
import {
|
|
14
|
+
ensureConfigFileExists,
|
|
15
|
+
readConfig,
|
|
16
|
+
getNodeAtPath,
|
|
17
|
+
buildRows,
|
|
18
|
+
deleteValueAtPathPruningEmptyObjects,
|
|
19
|
+
setValueAtPath,
|
|
20
|
+
writeConfig,
|
|
15
21
|
} from './src/configParser.js';
|
|
16
|
-
import { getConfigOptions } from './src/configHelp.js';
|
|
22
|
+
import { getConfigOptions, getConfigVariantMeta } from './src/configHelp.js';
|
|
17
23
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
getReferenceOptionForPath,
|
|
25
|
+
getReferenceCustomIdPlaceholder,
|
|
20
26
|
} from './src/configReference.js';
|
|
21
27
|
import { normalizeCustomPathId } from './src/customPathId.js';
|
|
22
|
-
import { pathToKey, clamp } from './src/layout.js';
|
|
23
28
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} from './src/
|
|
29
|
+
applyVariantSelection,
|
|
30
|
+
buildVariantSelectorOptions,
|
|
31
|
+
isObjectValue,
|
|
32
|
+
objectMatchesVariant,
|
|
33
|
+
resolveMixedVariantBackNavigationPath,
|
|
34
|
+
resolveObjectVariantNavigationPath,
|
|
35
|
+
} from './src/variantPresets.js';
|
|
36
|
+
import {
|
|
37
|
+
APP_MODES,
|
|
38
|
+
APP_STATE_ACTION,
|
|
39
|
+
REFERENCE_SCHEMA_CHANGED_ACTION,
|
|
40
|
+
appStateReducer,
|
|
41
|
+
buildInitialAppState,
|
|
42
|
+
} from './src/appState.js';
|
|
43
|
+
import { pathToKey, clamp, computeListViewportRows } from './src/layout.js';
|
|
31
44
|
import { Header } from './src/components/Header.js';
|
|
32
45
|
import { ConfigNavigator } from './src/components/ConfigNavigator.js';
|
|
33
46
|
import { filterRowsByQuery } from './src/fuzzySearch.js';
|
|
47
|
+
import { syncReferenceSchemaAtStartup } from './src/schemaRuntimeSync.js';
|
|
48
|
+
import { executeInputCommand, getModeHint } from './src/ui/commands.js';
|
|
49
|
+
import { CommandBar } from './src/ui/panes/CommandBar.js';
|
|
50
|
+
import { HelpBubble } from './src/ui/panes/HelpBubble.js';
|
|
51
|
+
import { LayoutShell } from './src/ui/panes/LayoutShell.js';
|
|
52
|
+
import { StatusLine } from './src/ui/panes/StatusLine.js';
|
|
34
53
|
|
|
35
54
|
const require = createRequire(import.meta.url);
|
|
36
55
|
const { version: PACKAGE_VERSION = 'unknown' } = require('./package.json');
|
|
37
56
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const isBooleanOnlyOptions = (options) =>
|
|
42
|
-
Array.isArray(options) &&
|
|
43
|
-
options.length === 2 &&
|
|
44
|
-
options.every((option) => typeof option === 'boolean') &&
|
|
45
|
-
options.includes(false) &&
|
|
46
|
-
options.includes(true);
|
|
47
|
-
|
|
48
|
-
const isStringReferenceType = (type) => /^string(?:\s|$)/.test(String(type || '').trim());
|
|
57
|
+
const isStringReferenceType = (type) =>
|
|
58
|
+
/^string(?:\s|$)/.test(String(type || '').trim());
|
|
49
59
|
|
|
50
60
|
const isStringField = (pathSegments, value) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
if (typeof value === 'string') {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
return isStringReferenceType(getReferenceOptionForPath(pathSegments)?.type);
|
|
56
66
|
};
|
|
57
67
|
|
|
58
68
|
const isCustomIdTableRow = (pathSegments, row) =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
row?.kind === 'table' &&
|
|
70
|
+
typeof row?.pathSegment === 'string' &&
|
|
71
|
+
Boolean(getReferenceCustomIdPlaceholder(pathSegments));
|
|
62
72
|
|
|
63
|
-
const isInlineTextMode = (mode) => mode === 'text' || mode === 'add-id';
|
|
64
73
|
const VERSION_COMMAND_TIMEOUT_MS = 3000;
|
|
65
74
|
const UPDATE_COMMAND_TIMEOUT_MS = 180000;
|
|
66
75
|
const COMMAND_MAX_BUFFER_BYTES = 1024 * 1024;
|
|
@@ -68,845 +77,1464 @@ const UPDATE_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
|
68
77
|
const CODEX_BIN_ENV_VAR = 'CODEX_CONFIGURATOR_CODEX_BIN';
|
|
69
78
|
const NPM_BIN_ENV_VAR = 'CODEX_CONFIGURATOR_NPM_BIN';
|
|
70
79
|
const CONFIGURATOR_PACKAGE_NAME = 'codex-configurator';
|
|
80
|
+
const SCHEMA_STATUS_MIN_VISIBLE_MS = 1200;
|
|
81
|
+
const FILE_SWITCH_MAX_VISIBLE_ENTRIES = 6;
|
|
82
|
+
const FILE_SWITCH_PANEL_BASE_ROWS = 3;
|
|
83
|
+
const FILE_SWITCH_LAYOUT_EXTRA_GAP_ROWS = 1;
|
|
84
|
+
const ENABLE_ALT_SCREEN = '\u001b[?1049h';
|
|
85
|
+
const DISABLE_ALT_SCREEN = '\u001b[?1049l';
|
|
86
|
+
const HIDE_CURSOR = '\u001b[?25l';
|
|
87
|
+
const SHOW_CURSOR = '\u001b[?25h';
|
|
88
|
+
const CLEAR_SCREEN = '\u001b[2J';
|
|
89
|
+
const CURSOR_HOME = '\u001b[H';
|
|
90
|
+
|
|
91
|
+
const supportsFullScreenTerminal = () =>
|
|
92
|
+
Boolean(process.stdout?.isTTY && process.stderr?.isTTY);
|
|
93
|
+
|
|
94
|
+
const setFullScreenState = (isActive) => {
|
|
95
|
+
if (!supportsFullScreenTerminal()) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isActive) {
|
|
100
|
+
process.stdout.write(
|
|
101
|
+
`${ENABLE_ALT_SCREEN}${CLEAR_SCREEN}${CURSOR_HOME}${HIDE_CURSOR}`,
|
|
102
|
+
);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
process.stdout.write(`${SHOW_CURSOR}${DISABLE_ALT_SCREEN}`);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const activateTerminalMode = () => {
|
|
110
|
+
if (!supportsFullScreenTerminal()) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let isActive = true;
|
|
115
|
+
setFullScreenState(true);
|
|
116
|
+
|
|
117
|
+
const restore = () => {
|
|
118
|
+
if (!isActive) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
isActive = false;
|
|
123
|
+
setFullScreenState(false);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
process.once('exit', restore);
|
|
127
|
+
process.once('SIGINT', () => {
|
|
128
|
+
restore();
|
|
129
|
+
process.exit(130);
|
|
130
|
+
});
|
|
131
|
+
process.once('SIGTERM', () => {
|
|
132
|
+
restore();
|
|
133
|
+
process.exit(143);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const computeFileSwitchPanelRows = (entryCount) => {
|
|
140
|
+
const totalEntries = Math.max(0, entryCount);
|
|
141
|
+
const visibleEntries = Math.min(
|
|
142
|
+
FILE_SWITCH_MAX_VISIBLE_ENTRIES,
|
|
143
|
+
totalEntries,
|
|
144
|
+
);
|
|
145
|
+
const hasOverflow = totalEntries > visibleEntries;
|
|
146
|
+
|
|
147
|
+
return FILE_SWITCH_PANEL_BASE_ROWS + visibleEntries + (hasOverflow ? 1 : 0);
|
|
148
|
+
};
|
|
71
149
|
|
|
72
150
|
const runCommandWithResult = (command, args = [], options = {}) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
151
|
+
new Promise((resolve) => {
|
|
152
|
+
execFile(
|
|
153
|
+
command,
|
|
154
|
+
args,
|
|
155
|
+
{
|
|
156
|
+
encoding: 'utf8',
|
|
157
|
+
timeout: options.timeout || VERSION_COMMAND_TIMEOUT_MS,
|
|
158
|
+
maxBuffer: options.maxBuffer || COMMAND_MAX_BUFFER_BYTES,
|
|
159
|
+
windowsHide: true,
|
|
160
|
+
},
|
|
161
|
+
(error, stdout, stderr) => {
|
|
162
|
+
resolve({
|
|
163
|
+
ok: !error,
|
|
164
|
+
stdout: String(stdout || '').trim(),
|
|
165
|
+
stderr: String(stderr || '').trim(),
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
});
|
|
92
170
|
|
|
93
171
|
const runCommand = async (command, args = [], options = {}) => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
172
|
+
const result = await runCommandWithResult(command, args, options);
|
|
173
|
+
if (!result.ok) {
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
98
176
|
|
|
99
|
-
|
|
177
|
+
return result.stdout;
|
|
100
178
|
};
|
|
101
179
|
|
|
180
|
+
const waitForMilliseconds = (milliseconds) =>
|
|
181
|
+
new Promise((resolve) => {
|
|
182
|
+
setTimeout(resolve, Math.max(0, Number(milliseconds) || 0));
|
|
183
|
+
});
|
|
184
|
+
|
|
102
185
|
const getConfiguredCommand = (environmentVariableName, fallbackCommand) => {
|
|
103
|
-
|
|
104
|
-
|
|
186
|
+
const configuredCommand = String(
|
|
187
|
+
process.env[environmentVariableName] || '',
|
|
188
|
+
).trim();
|
|
189
|
+
return configuredCommand || fallbackCommand;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const expandTildePath = (value, homeDir = os.homedir()) => {
|
|
193
|
+
const normalized = String(value || '').trim();
|
|
194
|
+
if (!normalized) {
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (normalized === '~') {
|
|
199
|
+
return homeDir;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (normalized.startsWith('~/') || normalized.startsWith('~\\')) {
|
|
203
|
+
return path.join(homeDir, normalized.slice(2));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return normalized;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const resolveAgentConfigFilePath = (mainConfigPath, configFileValue) => {
|
|
210
|
+
const normalizedValue = expandTildePath(configFileValue);
|
|
211
|
+
if (!normalizedValue) {
|
|
212
|
+
return '';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const mainPath =
|
|
216
|
+
String(mainConfigPath || '').trim() ||
|
|
217
|
+
path.resolve(process.cwd(), '.codex', 'config.toml');
|
|
218
|
+
const mainDirectory = path.dirname(mainPath);
|
|
219
|
+
|
|
220
|
+
return path.resolve(mainDirectory, normalizedValue);
|
|
105
221
|
};
|
|
106
222
|
|
|
107
223
|
const getVersionCommands = () => ({
|
|
108
|
-
|
|
109
|
-
|
|
224
|
+
codexCommand: getConfiguredCommand(CODEX_BIN_ENV_VAR, 'codex'),
|
|
225
|
+
npmCommand: getConfiguredCommand(NPM_BIN_ENV_VAR, 'npm'),
|
|
110
226
|
});
|
|
111
227
|
|
|
112
228
|
const getLatestPackageVersion = async (npmCommand, packageName) => {
|
|
113
|
-
|
|
114
|
-
|
|
229
|
+
const latestOutput = await runCommand(npmCommand, [
|
|
230
|
+
'view',
|
|
231
|
+
packageName,
|
|
232
|
+
'version',
|
|
233
|
+
'--json',
|
|
234
|
+
]);
|
|
235
|
+
return normalizeVersion(latestOutput) || latestOutput.trim();
|
|
115
236
|
};
|
|
116
237
|
|
|
117
238
|
const updateGlobalPackageToLatest = async (npmCommand, packageName) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
239
|
+
const result = await runCommandWithResult(
|
|
240
|
+
npmCommand,
|
|
241
|
+
['install', '-g', `${packageName}@latest`],
|
|
242
|
+
{
|
|
243
|
+
timeout: UPDATE_COMMAND_TIMEOUT_MS,
|
|
244
|
+
maxBuffer: UPDATE_MAX_BUFFER_BYTES,
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return result.ok;
|
|
128
249
|
};
|
|
129
250
|
|
|
130
251
|
const getCodexVersion = async (codexCommand) => {
|
|
131
|
-
|
|
132
|
-
|
|
252
|
+
const output = await runCommand(codexCommand, ['--version']);
|
|
253
|
+
const firstLine = output.split('\n')[0]?.trim();
|
|
133
254
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
255
|
+
if (!firstLine) {
|
|
256
|
+
return 'version unavailable';
|
|
257
|
+
}
|
|
137
258
|
|
|
138
|
-
|
|
259
|
+
return firstLine.startsWith('codex') ? firstLine : `version ${firstLine}`;
|
|
139
260
|
};
|
|
140
261
|
|
|
141
262
|
const normalizeVersion = (value) => {
|
|
142
|
-
|
|
143
|
-
|
|
263
|
+
const match = String(value || '').match(
|
|
264
|
+
/(\d+\.\d+\.\d+(?:[-+._][0-9A-Za-z.-]+)*)/,
|
|
265
|
+
);
|
|
266
|
+
return match ? match[1] : '';
|
|
144
267
|
};
|
|
145
268
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (a > b) {
|
|
163
|
-
return 1;
|
|
164
|
-
}
|
|
269
|
+
const parseVersion = (value) => {
|
|
270
|
+
const normalized = normalizeVersion(value);
|
|
271
|
+
const [core, ...suffixParts] = String(normalized || '').split(/[-+]/);
|
|
272
|
+
const suffix = suffixParts.join('-');
|
|
273
|
+
const coreParts = core
|
|
274
|
+
.split('.')
|
|
275
|
+
.map((part) => Number.parseInt(part, 10))
|
|
276
|
+
.filter(Number.isFinite);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
hasSuffix: Boolean(suffix),
|
|
280
|
+
suffix,
|
|
281
|
+
parts: coreParts,
|
|
282
|
+
};
|
|
283
|
+
};
|
|
165
284
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
285
|
+
const comparePreRelease = (leftSuffix, rightSuffix) => {
|
|
286
|
+
const leftParts = String(leftSuffix || '')
|
|
287
|
+
.split('.')
|
|
288
|
+
.filter(Boolean);
|
|
289
|
+
const rightParts = String(rightSuffix || '')
|
|
290
|
+
.split('.')
|
|
291
|
+
.filter(Boolean);
|
|
292
|
+
const maxLength = Math.max(leftParts.length, rightParts.length);
|
|
293
|
+
|
|
294
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
295
|
+
const leftPart = leftParts[index];
|
|
296
|
+
const rightPart = rightParts[index];
|
|
297
|
+
const leftNumber = Number.parseInt(leftPart, 10);
|
|
298
|
+
const rightNumber = Number.parseInt(rightPart, 10);
|
|
299
|
+
|
|
300
|
+
const leftIsNumber = Number.isFinite(leftNumber);
|
|
301
|
+
const rightIsNumber = Number.isFinite(rightNumber);
|
|
302
|
+
|
|
303
|
+
if (leftIsNumber && rightIsNumber) {
|
|
304
|
+
if (leftNumber > rightNumber) {
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (leftNumber < rightNumber) {
|
|
309
|
+
return -1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (leftIsNumber && !rightIsNumber) {
|
|
316
|
+
return -1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (!leftIsNumber && rightIsNumber) {
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if ((leftPart || '') > (rightPart || '')) {
|
|
324
|
+
return 1;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if ((leftPart || '') < (rightPart || '')) {
|
|
328
|
+
return -1;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return 0;
|
|
333
|
+
};
|
|
170
334
|
|
|
171
|
-
|
|
335
|
+
const compareVersions = (left, right) => {
|
|
336
|
+
const leftVersion = parseVersion(left);
|
|
337
|
+
const rightVersion = parseVersion(right);
|
|
338
|
+
const leftParts = leftVersion.parts;
|
|
339
|
+
const rightParts = rightVersion.parts;
|
|
340
|
+
const maxLength = Math.max(leftParts.length, rightParts.length);
|
|
341
|
+
|
|
342
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
343
|
+
const a = leftParts[index] || 0;
|
|
344
|
+
const b = rightParts[index] || 0;
|
|
345
|
+
|
|
346
|
+
if (a > b) {
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (a < b) {
|
|
351
|
+
return -1;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (leftVersion.hasSuffix !== rightVersion.hasSuffix) {
|
|
356
|
+
return leftVersion.hasSuffix ? -1 : 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (leftVersion.hasSuffix) {
|
|
360
|
+
return comparePreRelease(leftVersion.suffix, rightVersion.suffix);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return 0;
|
|
172
364
|
};
|
|
173
365
|
|
|
174
366
|
const getCodexUpdateStatus = async () => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
367
|
+
const commands = getVersionCommands();
|
|
368
|
+
const installedLabel = await getCodexVersion(commands.codexCommand);
|
|
369
|
+
const installed = normalizeVersion(installedLabel);
|
|
370
|
+
|
|
371
|
+
if (!installed) {
|
|
372
|
+
return {
|
|
373
|
+
installed: installedLabel,
|
|
374
|
+
latest: 'unknown',
|
|
375
|
+
status: 'Version check unavailable',
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const latestOutput = await runCommand(commands.npmCommand, [
|
|
380
|
+
'view',
|
|
381
|
+
'@openai/codex',
|
|
382
|
+
'version',
|
|
383
|
+
'--json',
|
|
384
|
+
]);
|
|
385
|
+
const latest = normalizeVersion(latestOutput) || latestOutput.trim();
|
|
386
|
+
|
|
387
|
+
if (!latest) {
|
|
388
|
+
return {
|
|
389
|
+
installed,
|
|
390
|
+
latest: 'unknown',
|
|
391
|
+
status: 'Version check unavailable',
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const comparison = compareVersions(installed, latest);
|
|
396
|
+
if (comparison < 0) {
|
|
397
|
+
return {
|
|
398
|
+
installed,
|
|
399
|
+
latest,
|
|
400
|
+
status: 'Update available',
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
installed,
|
|
406
|
+
latest,
|
|
407
|
+
status: 'Up to date',
|
|
408
|
+
};
|
|
217
409
|
};
|
|
218
410
|
|
|
219
411
|
const ensureLatestConfiguratorVersion = async (npmCommand) => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
412
|
+
const installed = normalizeVersion(PACKAGE_VERSION);
|
|
413
|
+
if (!installed) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const latest = await getLatestPackageVersion(
|
|
418
|
+
npmCommand,
|
|
419
|
+
CONFIGURATOR_PACKAGE_NAME,
|
|
420
|
+
);
|
|
421
|
+
if (!latest) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (compareVersions(installed, latest) >= 0) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
await updateGlobalPackageToLatest(npmCommand, CONFIGURATOR_PACKAGE_NAME);
|
|
235
430
|
};
|
|
236
431
|
|
|
237
432
|
const App = () => {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
433
|
+
const initialMainSnapshot = readConfig();
|
|
434
|
+
const initialCatalog = buildConfigFileCatalog(initialMainSnapshot);
|
|
435
|
+
const initialActiveFileId = initialCatalog[0]?.id || MAIN_CONFIG_FILE_ID;
|
|
436
|
+
|
|
437
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
438
|
+
const { stdout } = useStdout();
|
|
439
|
+
const defaultTerminalWidth = 100;
|
|
440
|
+
const defaultTerminalHeight = 24;
|
|
441
|
+
const [terminalSize, setTerminalSize] = useState({
|
|
442
|
+
width: stdout?.columns || defaultTerminalWidth,
|
|
443
|
+
height: stdout?.rows || defaultTerminalHeight,
|
|
444
|
+
});
|
|
445
|
+
const terminalWidth = terminalSize.width || defaultTerminalWidth;
|
|
446
|
+
const terminalHeight = terminalSize.height || defaultTerminalHeight;
|
|
447
|
+
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
const handleResize = () => {
|
|
450
|
+
const nextWidth = process.stdout?.columns || defaultTerminalWidth;
|
|
451
|
+
const nextHeight = process.stdout?.rows || defaultTerminalHeight;
|
|
452
|
+
setTerminalSize({
|
|
453
|
+
width: nextWidth,
|
|
454
|
+
height: nextHeight,
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
process.stdout?.on('resize', handleResize);
|
|
459
|
+
return () => {
|
|
460
|
+
process.stdout?.off('resize', handleResize);
|
|
461
|
+
};
|
|
462
|
+
}, []);
|
|
463
|
+
|
|
464
|
+
const [state, dispatch] = useReducer(
|
|
465
|
+
appStateReducer,
|
|
466
|
+
buildInitialAppState(
|
|
467
|
+
initialMainSnapshot,
|
|
468
|
+
initialCatalog,
|
|
469
|
+
initialActiveFileId,
|
|
470
|
+
),
|
|
471
|
+
);
|
|
472
|
+
const commandModeLockRef = useRef(false);
|
|
473
|
+
const commandInputRef = useRef('');
|
|
474
|
+
const setAppState = (key, valueOrUpdater) =>
|
|
475
|
+
dispatch({
|
|
476
|
+
type: APP_STATE_ACTION,
|
|
477
|
+
payload: { key, valueOrUpdater },
|
|
478
|
+
});
|
|
479
|
+
const setStateBatch = (updates) =>
|
|
480
|
+
dispatch({ type: APP_STATE_ACTION, payload: { updates } });
|
|
481
|
+
const resetSchemaDerivedEditState = () =>
|
|
482
|
+
dispatch({ type: REFERENCE_SCHEMA_CHANGED_ACTION });
|
|
483
|
+
|
|
484
|
+
const setSnapshot = (valueOrUpdater) =>
|
|
485
|
+
setAppState('snapshot', valueOrUpdater);
|
|
486
|
+
const setSnapshotByFileId = (valueOrUpdater) =>
|
|
487
|
+
setAppState('snapshotByFileId', valueOrUpdater);
|
|
488
|
+
const setConfigFileCatalog = (valueOrUpdater) =>
|
|
489
|
+
setAppState('configFileCatalog', valueOrUpdater);
|
|
490
|
+
const setSelectedIndex = (valueOrUpdater) =>
|
|
491
|
+
setAppState('selectedIndex', valueOrUpdater);
|
|
492
|
+
const setScrollOffset = (valueOrUpdater) =>
|
|
493
|
+
setAppState('scrollOffset', valueOrUpdater);
|
|
494
|
+
const setEditMode = (valueOrUpdater) =>
|
|
495
|
+
setAppState('editMode', valueOrUpdater);
|
|
496
|
+
const setFileSwitchIndex = (valueOrUpdater) =>
|
|
497
|
+
setAppState('fileSwitchIndex', valueOrUpdater);
|
|
498
|
+
const setEditError = (valueOrUpdater) =>
|
|
499
|
+
setAppState('editError', valueOrUpdater);
|
|
500
|
+
const setCommandMode = (valueOrUpdater) => {
|
|
501
|
+
const nextValue =
|
|
502
|
+
typeof valueOrUpdater === 'function'
|
|
503
|
+
? Boolean(valueOrUpdater(isCommandMode))
|
|
504
|
+
: Boolean(valueOrUpdater);
|
|
505
|
+
commandModeLockRef.current = nextValue;
|
|
506
|
+
setAppState('isCommandMode', nextValue);
|
|
507
|
+
};
|
|
508
|
+
const setCommandInput = (valueOrUpdater) => {
|
|
509
|
+
const previous = String(commandInputRef.current || '');
|
|
510
|
+
const resolved =
|
|
511
|
+
typeof valueOrUpdater === 'function'
|
|
512
|
+
? valueOrUpdater(previous)
|
|
513
|
+
: valueOrUpdater;
|
|
514
|
+
const next = String(resolved ?? '');
|
|
515
|
+
commandInputRef.current = next;
|
|
516
|
+
setAppState('commandInput', next);
|
|
517
|
+
};
|
|
518
|
+
const setCommandMessage = (valueOrUpdater) =>
|
|
519
|
+
setAppState('commandMessage', valueOrUpdater);
|
|
520
|
+
const setShowHelp = (valueOrUpdater) =>
|
|
521
|
+
setAppState('showHelp', valueOrUpdater);
|
|
522
|
+
const setFilterQuery = (valueOrUpdater) =>
|
|
523
|
+
setAppState('filterQuery', valueOrUpdater);
|
|
524
|
+
const setIsFilterEditing = (valueOrUpdater) =>
|
|
525
|
+
setAppState('isFilterEditing', valueOrUpdater);
|
|
526
|
+
const setCodexVersion = (valueOrUpdater) =>
|
|
527
|
+
setAppState('codexVersion', valueOrUpdater);
|
|
528
|
+
const setCodexVersionStatus = (valueOrUpdater) =>
|
|
529
|
+
setAppState('codexVersionStatus', valueOrUpdater);
|
|
530
|
+
const setIsSchemaCheckInProgress = (valueOrUpdater) =>
|
|
531
|
+
setAppState('isSchemaCheckInProgress', valueOrUpdater);
|
|
532
|
+
const setSchemaCheckStatusText = (valueOrUpdater) =>
|
|
533
|
+
setAppState('schemaCheckStatusText', valueOrUpdater);
|
|
534
|
+
const {
|
|
535
|
+
snapshot,
|
|
536
|
+
snapshotByFileId,
|
|
537
|
+
configFileCatalog,
|
|
538
|
+
activeConfigFileId,
|
|
539
|
+
pathSegments,
|
|
540
|
+
selectedIndex,
|
|
541
|
+
selectionByPath,
|
|
542
|
+
scrollOffset,
|
|
543
|
+
editMode,
|
|
544
|
+
isFileSwitchMode,
|
|
545
|
+
fileSwitchIndex,
|
|
546
|
+
editError,
|
|
547
|
+
filterQuery,
|
|
548
|
+
isFilterEditing,
|
|
549
|
+
isCommandMode,
|
|
550
|
+
commandInput,
|
|
551
|
+
commandMessage,
|
|
552
|
+
showHelp,
|
|
553
|
+
codexVersion,
|
|
554
|
+
codexVersionStatus,
|
|
555
|
+
isSchemaCheckInProgress,
|
|
556
|
+
schemaCheckStatusText,
|
|
557
|
+
} = state;
|
|
558
|
+
commandInputRef.current = String(commandInput || '');
|
|
559
|
+
const { exit } = useApp();
|
|
560
|
+
const appMode = isFilterEditing
|
|
561
|
+
? APP_MODES.FILTER
|
|
562
|
+
: isCommandMode
|
|
563
|
+
? APP_MODES.COMMAND
|
|
564
|
+
: isFileSwitchMode
|
|
565
|
+
? APP_MODES.FILE_SWITCH
|
|
566
|
+
: editMode
|
|
567
|
+
? APP_MODES.EDIT
|
|
568
|
+
: APP_MODES.BROWSE;
|
|
569
|
+
|
|
570
|
+
useEffect(() => {
|
|
571
|
+
if (isSchemaCheckInProgress) {
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let isCancelled = false;
|
|
576
|
+
|
|
577
|
+
const loadVersionStatus = async () => {
|
|
578
|
+
const check = await getCodexUpdateStatus();
|
|
579
|
+
|
|
580
|
+
if (isCancelled) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
setCodexVersion(check.installed);
|
|
585
|
+
setCodexVersionStatus(check.status);
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const ensureLatestConfigurator = async () => {
|
|
589
|
+
const commands = getVersionCommands();
|
|
590
|
+
await ensureLatestConfiguratorVersion(commands.npmCommand);
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
setCodexVersionStatus('Checking Codex version');
|
|
594
|
+
loadVersionStatus();
|
|
595
|
+
ensureLatestConfigurator();
|
|
596
|
+
|
|
597
|
+
return () => {
|
|
598
|
+
isCancelled = true;
|
|
599
|
+
};
|
|
600
|
+
}, [isSchemaCheckInProgress]);
|
|
601
|
+
|
|
602
|
+
useEffect(() => {
|
|
603
|
+
let isCancelled = false;
|
|
604
|
+
|
|
605
|
+
const syncSchemaReference = async () => {
|
|
606
|
+
const checkStartedAt = Date.now();
|
|
607
|
+
const updateSchemaStatus = (status) => {
|
|
608
|
+
if (isCancelled) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
setSchemaCheckStatusText(status);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
await syncReferenceSchemaAtStartup({
|
|
616
|
+
mainConfigPath: initialMainSnapshot.path,
|
|
617
|
+
onSchemaChange: () => {
|
|
618
|
+
if (isCancelled) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
resetSchemaDerivedEditState();
|
|
623
|
+
},
|
|
624
|
+
onStatus: updateSchemaStatus,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const elapsed = Date.now() - checkStartedAt;
|
|
628
|
+
const remainingVisibleMs = SCHEMA_STATUS_MIN_VISIBLE_MS - elapsed;
|
|
629
|
+
if (remainingVisibleMs > 0) {
|
|
630
|
+
await waitForMilliseconds(remainingVisibleMs);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (isCancelled) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
setSchemaCheckStatusText('');
|
|
638
|
+
setIsSchemaCheckInProgress(false);
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
syncSchemaReference();
|
|
642
|
+
|
|
643
|
+
return () => {
|
|
644
|
+
isCancelled = true;
|
|
645
|
+
};
|
|
646
|
+
}, []);
|
|
647
|
+
|
|
648
|
+
useEffect(() => {
|
|
649
|
+
const hasActiveFile = configFileCatalog.some(
|
|
650
|
+
(file) => file.id === activeConfigFileId,
|
|
651
|
+
);
|
|
652
|
+
if (hasActiveFile) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const fallbackFile = configFileCatalog[0];
|
|
657
|
+
if (!fallbackFile) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const fallbackSnapshot =
|
|
662
|
+
snapshotByFileId[fallbackFile.id] ||
|
|
663
|
+
(fallbackFile.kind === 'agent'
|
|
664
|
+
? ensureConfigFileExists(fallbackFile.path)
|
|
665
|
+
: readConfig(fallbackFile.path));
|
|
666
|
+
setStateBatch({
|
|
667
|
+
activeConfigFileId: fallbackFile.id,
|
|
668
|
+
snapshotByFileId: {
|
|
669
|
+
...snapshotByFileId,
|
|
670
|
+
...(snapshotByFileId[fallbackFile.id]
|
|
671
|
+
? {}
|
|
672
|
+
: {
|
|
673
|
+
[fallbackFile.id]: fallbackSnapshot,
|
|
674
|
+
}),
|
|
675
|
+
},
|
|
676
|
+
snapshot: fallbackSnapshot,
|
|
677
|
+
pathSegments: [],
|
|
678
|
+
selectedIndex: 0,
|
|
679
|
+
selectionByPath: {},
|
|
680
|
+
scrollOffset: 0,
|
|
681
|
+
});
|
|
682
|
+
}, [configFileCatalog, activeConfigFileId, snapshotByFileId]);
|
|
683
|
+
|
|
684
|
+
useEffect(() => {
|
|
685
|
+
if (!isFileSwitchMode) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
setFileSwitchIndex((previous) => {
|
|
690
|
+
const maxIndex = Math.max(0, configFileCatalog.length - 1);
|
|
691
|
+
return clamp(previous, 0, maxIndex);
|
|
692
|
+
});
|
|
693
|
+
}, [isFileSwitchMode, configFileCatalog]);
|
|
694
|
+
|
|
695
|
+
const activeConfigFile =
|
|
696
|
+
configFileCatalog.find((file) => file.id === activeConfigFileId) ||
|
|
697
|
+
configFileCatalog[0];
|
|
698
|
+
const activeConfigFilePath = activeConfigFile?.path || snapshot.path;
|
|
699
|
+
const readActiveConfigSnapshot = () => {
|
|
700
|
+
const activeEntry = resolveActiveFileEntry();
|
|
701
|
+
const targetPath = activeEntry?.path || activeConfigFilePath;
|
|
702
|
+
|
|
703
|
+
if (!activeEntry || activeEntry.kind !== 'agent') {
|
|
704
|
+
return readConfig(targetPath);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return ensureConfigFileExists(targetPath);
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const currentNode = getNodeAtPath(
|
|
711
|
+
snapshot.ok ? snapshot.data : {},
|
|
712
|
+
pathSegments,
|
|
713
|
+
);
|
|
714
|
+
const allRows = buildRows(currentNode, pathSegments);
|
|
715
|
+
const rows = filterRowsByQuery(allRows, filterQuery);
|
|
716
|
+
const safeSelected =
|
|
717
|
+
rows.length === 0 ? 0 : Math.min(selectedIndex, rows.length - 1);
|
|
718
|
+
const fileSwitchPanelExtraRows =
|
|
719
|
+
isInteractive && isFileSwitchMode
|
|
720
|
+
? computeFileSwitchPanelRows(configFileCatalog.length) +
|
|
721
|
+
FILE_SWITCH_LAYOUT_EXTRA_GAP_ROWS
|
|
722
|
+
: 0;
|
|
723
|
+
const listViewportHeight = computeListViewportRows({
|
|
724
|
+
terminalHeight,
|
|
725
|
+
terminalWidth,
|
|
726
|
+
activeConfigFile,
|
|
727
|
+
packageVersion: PACKAGE_VERSION,
|
|
728
|
+
codexVersion,
|
|
729
|
+
codexVersionStatus,
|
|
730
|
+
isInteractive,
|
|
731
|
+
isCommandMode,
|
|
732
|
+
extraChromeRows: fileSwitchPanelExtraRows,
|
|
733
|
+
});
|
|
734
|
+
const currentPathKey = `${activeConfigFileId}::${pathToKey(pathSegments)}`;
|
|
735
|
+
|
|
736
|
+
const getSavedIndex = (segments, fallback = 0) => {
|
|
737
|
+
const key = `${activeConfigFileId}::${pathToKey(segments)}`;
|
|
738
|
+
const maybe = selectionByPath[key];
|
|
739
|
+
|
|
740
|
+
if (Number.isInteger(maybe)) {
|
|
741
|
+
return maybe;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return fallback;
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
const adjustScrollForSelection = (
|
|
748
|
+
nextSelection,
|
|
749
|
+
nextViewportHeight,
|
|
750
|
+
totalRows,
|
|
751
|
+
) => {
|
|
752
|
+
const maxOffset = Math.max(0, totalRows - nextViewportHeight);
|
|
753
|
+
const minOffset = 0;
|
|
754
|
+
|
|
755
|
+
setScrollOffset((previous) => {
|
|
756
|
+
if (nextSelection < previous) {
|
|
757
|
+
return clamp(nextSelection, minOffset, maxOffset);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (nextSelection > previous + nextViewportHeight - 1) {
|
|
761
|
+
return clamp(
|
|
762
|
+
nextSelection - nextViewportHeight + 1,
|
|
763
|
+
minOffset,
|
|
764
|
+
maxOffset,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return clamp(previous, minOffset, maxOffset);
|
|
769
|
+
});
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const updateActiveSnapshot = (nextSnapshot) => {
|
|
773
|
+
setSnapshot(nextSnapshot);
|
|
774
|
+
setSnapshotByFileId((previous) => ({
|
|
775
|
+
...previous,
|
|
776
|
+
[activeConfigFileId]: nextSnapshot,
|
|
777
|
+
}));
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
const ensureAgentConfigFile = (nextData, editedPath) => {
|
|
781
|
+
if (activeConfigFileId !== MAIN_CONFIG_FILE_ID) {
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const isAgentConfigFilePath =
|
|
786
|
+
Array.isArray(editedPath) &&
|
|
787
|
+
editedPath.length === 3 &&
|
|
788
|
+
editedPath[0] === 'agents' &&
|
|
789
|
+
editedPath[2] === 'config_file';
|
|
790
|
+
if (!isAgentConfigFilePath) {
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const configFileValue = getNodeAtPath(nextData, editedPath);
|
|
795
|
+
if (typeof configFileValue !== 'string' || !configFileValue.trim()) {
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const normalizedTarget = resolveAgentConfigFilePath(
|
|
800
|
+
activeConfigFile?.path || snapshot.path,
|
|
801
|
+
configFileValue,
|
|
802
|
+
);
|
|
803
|
+
if (!normalizedTarget) {
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const ensureResult = ensureConfigFileExists(normalizedTarget);
|
|
808
|
+
if (!ensureResult.ok) {
|
|
809
|
+
setEditError(ensureResult.error);
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
setSnapshotByFileId((previous) => ({
|
|
814
|
+
...previous,
|
|
815
|
+
[`agent:${normalizedTarget}`]:
|
|
816
|
+
previous[`agent:${normalizedTarget}`] || ensureResult,
|
|
817
|
+
}));
|
|
818
|
+
return true;
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const resolveActiveFileEntry = () =>
|
|
822
|
+
configFileCatalog.find((file) => file.id === activeConfigFileId);
|
|
823
|
+
|
|
824
|
+
const refreshConfigFileCatalog = (mainSnapshot) => {
|
|
825
|
+
const nextCatalog = buildConfigFileCatalog(mainSnapshot);
|
|
826
|
+
setConfigFileCatalog(nextCatalog);
|
|
827
|
+
return nextCatalog;
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
const switchConfigFile = (nextFileId) => {
|
|
831
|
+
const nextFile = configFileCatalog.find(
|
|
832
|
+
(file) => file.id === nextFileId,
|
|
833
|
+
);
|
|
834
|
+
if (!nextFile) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const nextSnapshot =
|
|
839
|
+
snapshotByFileId[nextFileId] ||
|
|
840
|
+
(nextFile.kind === 'agent'
|
|
841
|
+
? ensureConfigFileExists(nextFile.path)
|
|
842
|
+
: readConfig(nextFile.path));
|
|
843
|
+
if (!snapshotByFileId[nextFileId]) {
|
|
844
|
+
setSnapshotByFileId((previous) => ({
|
|
845
|
+
...previous,
|
|
846
|
+
[nextFileId]: nextSnapshot,
|
|
847
|
+
}));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (nextFileId === activeConfigFileId) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
setStateBatch({
|
|
855
|
+
activeConfigFileId: nextFileId,
|
|
856
|
+
snapshot: nextSnapshot,
|
|
857
|
+
pathSegments: [],
|
|
858
|
+
selectedIndex: 0,
|
|
859
|
+
scrollOffset: 0,
|
|
860
|
+
editMode: null,
|
|
861
|
+
isFileSwitchMode: false,
|
|
862
|
+
editError: '',
|
|
863
|
+
});
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
const beginEditing = (target, targetPath) => {
|
|
867
|
+
const options =
|
|
868
|
+
getConfigOptions(
|
|
869
|
+
targetPath,
|
|
870
|
+
target.key,
|
|
871
|
+
target.value,
|
|
872
|
+
target.kind,
|
|
873
|
+
) || [];
|
|
874
|
+
if (options.length === 0) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
setEditError('');
|
|
879
|
+
setEditMode({
|
|
880
|
+
mode: 'select',
|
|
881
|
+
path: targetPath,
|
|
882
|
+
options,
|
|
883
|
+
selectedOptionIndex: clamp(
|
|
884
|
+
options.findIndex((option) => Object.is(option, target.value)),
|
|
885
|
+
0,
|
|
886
|
+
options.length - 1,
|
|
887
|
+
),
|
|
888
|
+
savedOptionIndex: null,
|
|
889
|
+
});
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
const beginTextEditing = (target, targetPath) => {
|
|
893
|
+
setEditError('');
|
|
894
|
+
setEditMode({
|
|
895
|
+
mode: 'text',
|
|
896
|
+
path: targetPath,
|
|
897
|
+
draftValue: typeof target.value === 'string' ? target.value : '',
|
|
898
|
+
savedValue: null,
|
|
899
|
+
});
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
const beginAddIdEditing = (targetPath, placeholder = 'id') => {
|
|
903
|
+
setEditError('');
|
|
904
|
+
setEditMode({
|
|
905
|
+
mode: 'add-id',
|
|
906
|
+
path: targetPath,
|
|
907
|
+
placeholder,
|
|
908
|
+
draftValue: '',
|
|
909
|
+
savedValue: null,
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
const beginVariantEditing = (target, targetPath, variantMeta) => {
|
|
914
|
+
if (variantMeta?.kind !== 'scalar_object') {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const variantOptions = buildVariantSelectorOptions(variantMeta);
|
|
919
|
+
if (variantOptions.length === 0) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const currentVariantIndex = isObjectValue(target.value)
|
|
924
|
+
? variantOptions.findIndex(
|
|
925
|
+
(option) =>
|
|
926
|
+
option.kind === 'object' &&
|
|
927
|
+
objectMatchesVariant(target.value, option),
|
|
928
|
+
)
|
|
929
|
+
: variantOptions.findIndex(
|
|
930
|
+
(option) =>
|
|
931
|
+
option.kind === 'scalar' &&
|
|
932
|
+
Object.is(option.value, String(target.value)),
|
|
933
|
+
);
|
|
934
|
+
const selectedOptionIndex =
|
|
935
|
+
currentVariantIndex >= 0 ? currentVariantIndex : 0;
|
|
936
|
+
|
|
937
|
+
setEditError('');
|
|
938
|
+
setEditMode({
|
|
939
|
+
mode: 'variant-select',
|
|
940
|
+
key: target.key,
|
|
941
|
+
path: targetPath,
|
|
942
|
+
options: variantOptions.map((option) => option.label),
|
|
943
|
+
variantOptions,
|
|
944
|
+
selectedOptionIndex: clamp(
|
|
945
|
+
selectedOptionIndex,
|
|
946
|
+
0,
|
|
947
|
+
variantOptions.length - 1,
|
|
948
|
+
),
|
|
949
|
+
savedOptionIndex: null,
|
|
950
|
+
});
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
const openPathView = (nextPath, nextData) => {
|
|
954
|
+
const data =
|
|
955
|
+
typeof nextData === 'undefined'
|
|
956
|
+
? snapshot.ok
|
|
957
|
+
? snapshot.data
|
|
958
|
+
: {}
|
|
959
|
+
: nextData;
|
|
960
|
+
const nextNode = getNodeAtPath(data, nextPath);
|
|
961
|
+
const nextRows = buildRows(nextNode, nextPath);
|
|
962
|
+
const nextViewportHeight = computeListViewportRows({
|
|
963
|
+
terminalHeight,
|
|
964
|
+
terminalWidth,
|
|
965
|
+
activeConfigFile,
|
|
966
|
+
packageVersion: PACKAGE_VERSION,
|
|
967
|
+
codexVersion,
|
|
968
|
+
codexVersionStatus,
|
|
969
|
+
isInteractive,
|
|
970
|
+
isCommandMode,
|
|
971
|
+
extraChromeRows: fileSwitchPanelExtraRows,
|
|
972
|
+
});
|
|
973
|
+
const nextSavedIndex = getSavedIndex(nextPath, 0);
|
|
974
|
+
const nextSelected =
|
|
975
|
+
nextRows.length === 0
|
|
976
|
+
? 0
|
|
977
|
+
: clamp(nextSavedIndex, 0, nextRows.length - 1);
|
|
978
|
+
|
|
979
|
+
setStateBatch({
|
|
980
|
+
selectionByPath: {
|
|
981
|
+
...selectionByPath,
|
|
982
|
+
[currentPathKey]: safeSelected,
|
|
983
|
+
},
|
|
984
|
+
pathSegments: nextPath,
|
|
985
|
+
selectedIndex: nextSelected,
|
|
986
|
+
scrollOffset: clamp(
|
|
987
|
+
nextSelected,
|
|
988
|
+
0,
|
|
989
|
+
Math.max(0, nextRows.length - nextViewportHeight),
|
|
990
|
+
),
|
|
991
|
+
});
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const applyEdit = () => {
|
|
995
|
+
if (!editMode || editMode.mode !== 'select') {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const nextIndex = editMode.selectedOptionIndex;
|
|
1000
|
+
const nextValue = editMode.options[nextIndex];
|
|
1001
|
+
const nextData = setValueAtPath(
|
|
1002
|
+
snapshot.ok ? snapshot.data : {},
|
|
1003
|
+
editMode.path,
|
|
1004
|
+
nextValue,
|
|
1005
|
+
);
|
|
1006
|
+
if (!ensureAgentConfigFile(nextData, editMode.path)) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const writeResult = writeConfig(nextData, snapshot.path);
|
|
1011
|
+
|
|
1012
|
+
if (!writeResult.ok) {
|
|
1013
|
+
setEditError(writeResult.error);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const nextSnapshot = {
|
|
1018
|
+
ok: true,
|
|
1019
|
+
path: snapshot.path,
|
|
1020
|
+
data: nextData,
|
|
1021
|
+
};
|
|
1022
|
+
updateActiveSnapshot(nextSnapshot);
|
|
1023
|
+
|
|
1024
|
+
if (activeConfigFileId === MAIN_CONFIG_FILE_ID) {
|
|
1025
|
+
refreshConfigFileCatalog(nextSnapshot);
|
|
1026
|
+
}
|
|
1027
|
+
setEditMode(null);
|
|
1028
|
+
setEditError('');
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
const applyTextEdit = () => {
|
|
1032
|
+
if (!editMode || editMode.mode !== 'text') {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const nextData = setValueAtPath(
|
|
1037
|
+
snapshot.ok ? snapshot.data : {},
|
|
1038
|
+
editMode.path,
|
|
1039
|
+
editMode.draftValue,
|
|
1040
|
+
);
|
|
1041
|
+
if (!ensureAgentConfigFile(nextData, editMode.path)) {
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const writeResult = writeConfig(nextData, snapshot.path);
|
|
1046
|
+
|
|
1047
|
+
if (!writeResult.ok) {
|
|
1048
|
+
setEditError(writeResult.error);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const nextSnapshot = {
|
|
1053
|
+
ok: true,
|
|
1054
|
+
path: snapshot.path,
|
|
1055
|
+
data: nextData,
|
|
1056
|
+
};
|
|
1057
|
+
updateActiveSnapshot(nextSnapshot);
|
|
1058
|
+
|
|
1059
|
+
if (activeConfigFileId === MAIN_CONFIG_FILE_ID) {
|
|
1060
|
+
refreshConfigFileCatalog(nextSnapshot);
|
|
1061
|
+
}
|
|
1062
|
+
setEditMode(null);
|
|
1063
|
+
setEditError('');
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
const applyAddId = () => {
|
|
1067
|
+
if (!editMode || editMode.mode !== 'add-id') {
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const nextIdInput = String(editMode.draftValue || '').trim();
|
|
1072
|
+
const placeholder = getReferenceCustomIdPlaceholder(editMode.path);
|
|
1073
|
+
let nextId = nextIdInput;
|
|
1074
|
+
|
|
1075
|
+
if (placeholder === '<path>') {
|
|
1076
|
+
const normalizedPath = normalizeCustomPathId(nextIdInput);
|
|
1077
|
+
if (!normalizedPath.ok) {
|
|
1078
|
+
setEditError(normalizedPath.error);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
nextId = normalizedPath.value;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (!nextId) {
|
|
1086
|
+
setEditError('ID cannot be empty.');
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const nextPath = [...editMode.path, nextId];
|
|
1091
|
+
const data = snapshot.ok ? snapshot.data : {};
|
|
1092
|
+
const existingValue = getNodeAtPath(data, nextPath);
|
|
1093
|
+
|
|
1094
|
+
if (typeof existingValue !== 'undefined') {
|
|
1095
|
+
setEditError(`ID "${nextId}" already exists.`);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
openPathView(nextPath, data);
|
|
1100
|
+
setEditMode(null);
|
|
1101
|
+
setEditError('');
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
const applyVariantEdit = () => {
|
|
1105
|
+
if (!editMode || editMode.mode !== 'variant-select') {
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const selectedVariant = Array.isArray(editMode.variantOptions)
|
|
1110
|
+
? editMode.variantOptions[editMode.selectedOptionIndex]
|
|
1111
|
+
: null;
|
|
1112
|
+
if (!selectedVariant) {
|
|
1113
|
+
setEditMode(null);
|
|
1114
|
+
setEditError('');
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const data = snapshot.ok ? snapshot.data : {};
|
|
1119
|
+
const currentValue = getNodeAtPath(data, editMode.path);
|
|
1120
|
+
const selectionResult = applyVariantSelection({
|
|
1121
|
+
currentValue,
|
|
1122
|
+
selectedVariant,
|
|
1123
|
+
resolveDefaultValue: (requiredKey) => {
|
|
1124
|
+
const requiredPath = [...editMode.path, requiredKey];
|
|
1125
|
+
const requiredOptions =
|
|
1126
|
+
getConfigOptions(
|
|
1127
|
+
requiredPath,
|
|
1128
|
+
requiredKey,
|
|
1129
|
+
undefined,
|
|
1130
|
+
'value',
|
|
1131
|
+
) || [];
|
|
1132
|
+
if (requiredOptions.length > 0) {
|
|
1133
|
+
return requiredOptions[0];
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (
|
|
1137
|
+
isStringReferenceType(
|
|
1138
|
+
getReferenceOptionForPath(requiredPath)?.type,
|
|
1139
|
+
)
|
|
1140
|
+
) {
|
|
1141
|
+
return '';
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
return {};
|
|
1145
|
+
},
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
const shouldPersistSelection =
|
|
1149
|
+
selectionResult.changed &&
|
|
1150
|
+
(!selectionResult.isObjectSelection ||
|
|
1151
|
+
selectionResult.isObjectVariantSwitch);
|
|
1152
|
+
let nextData = data;
|
|
1153
|
+
if (shouldPersistSelection) {
|
|
1154
|
+
nextData = setValueAtPath(
|
|
1155
|
+
data,
|
|
1156
|
+
editMode.path,
|
|
1157
|
+
selectionResult.nextValue,
|
|
1158
|
+
);
|
|
1159
|
+
const writeResult = writeConfig(nextData, snapshot.path);
|
|
1160
|
+
|
|
1161
|
+
if (!writeResult.ok) {
|
|
1162
|
+
setEditError(writeResult.error);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const nextSnapshot = {
|
|
1167
|
+
ok: true,
|
|
1168
|
+
path: snapshot.path,
|
|
1169
|
+
data: nextData,
|
|
1170
|
+
};
|
|
1171
|
+
updateActiveSnapshot(nextSnapshot);
|
|
1172
|
+
|
|
1173
|
+
if (activeConfigFileId === MAIN_CONFIG_FILE_ID) {
|
|
1174
|
+
refreshConfigFileCatalog(nextSnapshot);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (selectionResult.navigateToObject) {
|
|
1179
|
+
const nextPath = resolveObjectVariantNavigationPath({
|
|
1180
|
+
basePath: editMode.path,
|
|
1181
|
+
nextValue: selectionResult.nextValue,
|
|
1182
|
+
preferredKey:
|
|
1183
|
+
selectedVariant.kind === 'object' &&
|
|
1184
|
+
selectedVariant.requiredKeys.length === 1
|
|
1185
|
+
? selectedVariant.requiredKeys[0]
|
|
1186
|
+
: null,
|
|
1187
|
+
});
|
|
1188
|
+
openPathView(nextPath, nextData);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
setEditError('');
|
|
1192
|
+
setEditMode(null);
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
const reloadActiveConfig = () => {
|
|
1196
|
+
const nextSnapshot = readActiveConfigSnapshot();
|
|
1197
|
+
updateActiveSnapshot(nextSnapshot);
|
|
1198
|
+
setStateBatch({
|
|
1199
|
+
pathSegments: [],
|
|
1200
|
+
selectedIndex: 0,
|
|
1201
|
+
selectionByPath: {},
|
|
1202
|
+
scrollOffset: 0,
|
|
1203
|
+
editMode: null,
|
|
1204
|
+
editError: '',
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
if (activeConfigFileId === MAIN_CONFIG_FILE_ID) {
|
|
1208
|
+
refreshConfigFileCatalog(nextSnapshot);
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
const beginFileSwitchMode = () => {
|
|
1213
|
+
if (
|
|
1214
|
+
!Array.isArray(configFileCatalog) ||
|
|
1215
|
+
configFileCatalog.length === 0
|
|
1216
|
+
) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (configFileCatalog.length === 1) {
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
setStateBatch({
|
|
1225
|
+
editError: '',
|
|
1226
|
+
isFileSwitchMode: true,
|
|
1227
|
+
fileSwitchIndex: Math.max(
|
|
1228
|
+
0,
|
|
1229
|
+
configFileCatalog.findIndex(
|
|
1230
|
+
(file) => file.id === activeConfigFileId,
|
|
1231
|
+
),
|
|
1232
|
+
),
|
|
1233
|
+
});
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
const applyFileSwitch = () => {
|
|
1237
|
+
if (!isFileSwitchMode) {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const nextFile = configFileCatalog[fileSwitchIndex];
|
|
1242
|
+
if (!nextFile) {
|
|
1243
|
+
setStateBatch({ isFileSwitchMode: false, editError: '' });
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
switchConfigFile(nextFile.id);
|
|
1248
|
+
setStateBatch({ isFileSwitchMode: false, editError: '' });
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1251
|
+
const applyBooleanToggle = (target, targetPath) => {
|
|
1252
|
+
const nextValue = !target.value;
|
|
1253
|
+
const data = snapshot.ok ? snapshot.data : {};
|
|
1254
|
+
const nextData = setValueAtPath(data, targetPath, nextValue);
|
|
1255
|
+
|
|
1256
|
+
const writeResult = writeConfig(nextData, snapshot.path);
|
|
1257
|
+
|
|
1258
|
+
if (!writeResult.ok) {
|
|
1259
|
+
setEditError(writeResult.error);
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const nextSnapshot = {
|
|
1264
|
+
ok: true,
|
|
1265
|
+
path: snapshot.path,
|
|
1266
|
+
data: nextData,
|
|
1267
|
+
};
|
|
1268
|
+
updateActiveSnapshot(nextSnapshot);
|
|
1269
|
+
|
|
1270
|
+
if (activeConfigFileId === MAIN_CONFIG_FILE_ID) {
|
|
1271
|
+
refreshConfigFileCatalog(nextSnapshot);
|
|
1272
|
+
}
|
|
1273
|
+
setEditError('');
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
const unsetValueAtPath = (targetPath) => {
|
|
1277
|
+
const data = snapshot.ok ? snapshot.data : {};
|
|
1278
|
+
const hasConfiguredValue =
|
|
1279
|
+
typeof getNodeAtPath(data, targetPath) !== 'undefined';
|
|
1280
|
+
|
|
1281
|
+
if (!hasConfiguredValue) {
|
|
1282
|
+
setEditError('');
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const nextData = deleteValueAtPathPruningEmptyObjects(data, targetPath);
|
|
1287
|
+
const writeResult = writeConfig(nextData, snapshot.path);
|
|
1288
|
+
|
|
1289
|
+
if (!writeResult.ok) {
|
|
1290
|
+
setEditError(writeResult.error);
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const nextSnapshot = {
|
|
1295
|
+
ok: true,
|
|
1296
|
+
path: snapshot.path,
|
|
1297
|
+
data: nextData,
|
|
1298
|
+
};
|
|
1299
|
+
updateActiveSnapshot(nextSnapshot);
|
|
1300
|
+
|
|
1301
|
+
if (activeConfigFileId === MAIN_CONFIG_FILE_ID) {
|
|
1302
|
+
refreshConfigFileCatalog(nextSnapshot);
|
|
1303
|
+
}
|
|
1304
|
+
setEditError('');
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
useInput(
|
|
1308
|
+
(input, key) => {
|
|
1309
|
+
const commandHandled = executeInputCommand({
|
|
1310
|
+
input,
|
|
1311
|
+
key,
|
|
1312
|
+
context: {
|
|
1313
|
+
appMode,
|
|
1314
|
+
isFilterEditing,
|
|
1315
|
+
isFileSwitchMode,
|
|
1316
|
+
isCommandMode,
|
|
1317
|
+
isCommandModeLocked: commandModeLockRef.current,
|
|
1318
|
+
activeConfigFileId,
|
|
1319
|
+
rows,
|
|
1320
|
+
safeSelected,
|
|
1321
|
+
editMode,
|
|
1322
|
+
listViewportHeight,
|
|
1323
|
+
pathSegments,
|
|
1324
|
+
snapshot,
|
|
1325
|
+
currentNode,
|
|
1326
|
+
terminalHeight,
|
|
1327
|
+
selectionByPath,
|
|
1328
|
+
configFileCatalog,
|
|
1329
|
+
fileSwitchIndex,
|
|
1330
|
+
currentPathKey,
|
|
1331
|
+
clamp,
|
|
1332
|
+
setEditMode,
|
|
1333
|
+
setFileSwitchIndex,
|
|
1334
|
+
setIsFilterEditing,
|
|
1335
|
+
setFilterQuery,
|
|
1336
|
+
setShowHelp,
|
|
1337
|
+
setStateBatch,
|
|
1338
|
+
setSelectedIndex,
|
|
1339
|
+
setCommandMode,
|
|
1340
|
+
setCommandInput,
|
|
1341
|
+
getCommandInput: () => commandInputRef.current,
|
|
1342
|
+
setCommandMessage,
|
|
1343
|
+
setEditError,
|
|
1344
|
+
beginAddIdEditing,
|
|
1345
|
+
beginTextEditing,
|
|
1346
|
+
beginVariantEditing,
|
|
1347
|
+
beginEditing,
|
|
1348
|
+
beginFileSwitchMode,
|
|
1349
|
+
applyFileSwitch,
|
|
1350
|
+
applyTextEdit,
|
|
1351
|
+
applyAddId,
|
|
1352
|
+
applyVariantEdit,
|
|
1353
|
+
applyEdit,
|
|
1354
|
+
applyBooleanToggle,
|
|
1355
|
+
unsetValueAtPath,
|
|
1356
|
+
openPathView,
|
|
1357
|
+
reloadActiveConfig,
|
|
1358
|
+
getConfigOptions: getConfigOptions,
|
|
1359
|
+
getConfigVariantMeta,
|
|
1360
|
+
getNodeAtPath,
|
|
1361
|
+
buildRows,
|
|
1362
|
+
isStringField,
|
|
1363
|
+
isCustomIdTableRow,
|
|
1364
|
+
resolveMixedVariantBackNavigationPath,
|
|
1365
|
+
adjustScrollForSelection,
|
|
1366
|
+
getSavedIndex,
|
|
1367
|
+
readActiveConfigSnapshot,
|
|
1368
|
+
refreshConfigFileCatalog,
|
|
1369
|
+
exit,
|
|
1370
|
+
},
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
if (commandHandled) {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
{ isActive: isInteractive },
|
|
1378
|
+
);
|
|
1379
|
+
|
|
1380
|
+
useEffect(() => {
|
|
1381
|
+
const maxOffset = Math.max(0, rows.length - listViewportHeight);
|
|
1382
|
+
setScrollOffset((previous) => clamp(previous, 0, maxOffset));
|
|
1383
|
+
}, [rows.length, listViewportHeight]);
|
|
1384
|
+
|
|
1385
|
+
const renderFileSwitchPanel = () => {
|
|
1386
|
+
if (!isFileSwitchMode || configFileCatalog.length === 0) {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
const totalEntries = configFileCatalog.length;
|
|
1391
|
+
const visibleEntries = Math.min(
|
|
1392
|
+
FILE_SWITCH_MAX_VISIBLE_ENTRIES,
|
|
1393
|
+
totalEntries,
|
|
1394
|
+
);
|
|
1395
|
+
const maxStartIndex = Math.max(0, totalEntries - visibleEntries);
|
|
1396
|
+
const startIndex = clamp(
|
|
1397
|
+
fileSwitchIndex - Math.floor(visibleEntries / 2),
|
|
1398
|
+
0,
|
|
1399
|
+
maxStartIndex,
|
|
1400
|
+
);
|
|
1401
|
+
const endIndex = Math.min(totalEntries, startIndex + visibleEntries);
|
|
1402
|
+
const visibleFiles = configFileCatalog.slice(startIndex, endIndex);
|
|
1403
|
+
const hasOverflow = totalEntries > visibleEntries;
|
|
1404
|
+
|
|
1405
|
+
return React.createElement(
|
|
1406
|
+
Box,
|
|
1407
|
+
{
|
|
1408
|
+
borderStyle: 'round',
|
|
1409
|
+
borderColor: 'cyan',
|
|
1410
|
+
paddingX: 1,
|
|
1411
|
+
flexDirection: 'column',
|
|
1412
|
+
},
|
|
1413
|
+
React.createElement(
|
|
1414
|
+
Text,
|
|
1415
|
+
{ bold: true, color: 'cyan' },
|
|
1416
|
+
'File Switch',
|
|
1417
|
+
),
|
|
1418
|
+
...visibleFiles.map((file, offsetIndex) => {
|
|
1419
|
+
const index = startIndex + offsetIndex;
|
|
1420
|
+
const isSelected = index === fileSwitchIndex;
|
|
1421
|
+
const isActiveFile = file.id === activeConfigFileId;
|
|
1422
|
+
const fileLabel = `${file.label} (${file.kind === 'main' ? 'main' : 'agent'})`;
|
|
1423
|
+
return React.createElement(
|
|
1424
|
+
Text,
|
|
1425
|
+
{
|
|
1426
|
+
key: file.id,
|
|
1427
|
+
color: isSelected
|
|
1428
|
+
? 'yellow'
|
|
1429
|
+
: isActiveFile
|
|
1430
|
+
? 'green'
|
|
1431
|
+
: 'gray',
|
|
1432
|
+
bold: isSelected,
|
|
1433
|
+
wrap: 'truncate-end',
|
|
1434
|
+
},
|
|
1435
|
+
`${isSelected ? '› ' : ' '}${fileLabel}${isActiveFile ? ' [active]' : ''}`,
|
|
1436
|
+
);
|
|
1437
|
+
}),
|
|
1438
|
+
hasOverflow
|
|
1439
|
+
? React.createElement(
|
|
1440
|
+
Text,
|
|
1441
|
+
{
|
|
1442
|
+
color: 'gray',
|
|
1443
|
+
wrap: 'truncate-end',
|
|
1444
|
+
key: 'file-switch-window',
|
|
1445
|
+
},
|
|
1446
|
+
`Showing ${startIndex + 1}-${endIndex} of ${totalEntries}`,
|
|
1447
|
+
)
|
|
1448
|
+
: null,
|
|
1449
|
+
);
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
const commandModeHint = getModeHint({
|
|
1453
|
+
appMode,
|
|
1454
|
+
isCommandMode,
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
if (!isInteractive) {
|
|
1458
|
+
return React.createElement(
|
|
1459
|
+
Box,
|
|
1460
|
+
{ flexDirection: 'column', padding: 1 },
|
|
1461
|
+
React.createElement(Header, {
|
|
1462
|
+
packageVersion: PACKAGE_VERSION,
|
|
1463
|
+
terminalWidth,
|
|
1464
|
+
}),
|
|
1465
|
+
React.createElement(ConfigNavigator, {
|
|
1466
|
+
snapshot,
|
|
1467
|
+
pathSegments,
|
|
1468
|
+
selectedIndex: 0,
|
|
1469
|
+
terminalWidth,
|
|
1470
|
+
listViewportHeight,
|
|
1471
|
+
scrollOffset: 0,
|
|
1472
|
+
editMode: null,
|
|
1473
|
+
editError: editError,
|
|
1474
|
+
filterQuery,
|
|
1475
|
+
isFilterEditing,
|
|
1476
|
+
activeConfigFile: activeConfigFile,
|
|
1477
|
+
}),
|
|
1478
|
+
React.createElement(
|
|
1479
|
+
Text,
|
|
1480
|
+
{ color: 'yellow' },
|
|
1481
|
+
'Non-interactive mode: input is disabled.',
|
|
1482
|
+
),
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
return React.createElement(
|
|
1487
|
+
LayoutShell,
|
|
1488
|
+
null,
|
|
1489
|
+
React.createElement(Header, {
|
|
1490
|
+
packageVersion: PACKAGE_VERSION,
|
|
1491
|
+
terminalWidth,
|
|
1492
|
+
}),
|
|
1493
|
+
React.createElement(ConfigNavigator, {
|
|
1494
|
+
snapshot,
|
|
1495
|
+
pathSegments,
|
|
1496
|
+
selectedIndex: safeSelected,
|
|
1497
|
+
terminalWidth,
|
|
1498
|
+
listViewportHeight,
|
|
1499
|
+
scrollOffset,
|
|
1500
|
+
editMode,
|
|
1501
|
+
editError,
|
|
1502
|
+
filterQuery,
|
|
1503
|
+
isFilterEditing,
|
|
1504
|
+
activeConfigFile: activeConfigFile,
|
|
1505
|
+
}),
|
|
1506
|
+
renderFileSwitchPanel(),
|
|
1507
|
+
React.createElement(CommandBar, {
|
|
1508
|
+
appMode,
|
|
1509
|
+
isCommandMode,
|
|
1510
|
+
commandInput,
|
|
1511
|
+
commandMessage,
|
|
1512
|
+
modeHint: commandModeHint,
|
|
1513
|
+
}),
|
|
1514
|
+
React.createElement(StatusLine, {
|
|
1515
|
+
codexVersion,
|
|
1516
|
+
codexVersionStatus,
|
|
1517
|
+
isSchemaCheckInProgress,
|
|
1518
|
+
schemaCheckStatusText,
|
|
1519
|
+
activeConfigFile,
|
|
1520
|
+
appMode,
|
|
1521
|
+
}),
|
|
1522
|
+
showHelp
|
|
1523
|
+
? React.createElement(
|
|
1524
|
+
Box,
|
|
1525
|
+
{
|
|
1526
|
+
position: 'absolute',
|
|
1527
|
+
bottom: 3,
|
|
1528
|
+
left: 0,
|
|
1529
|
+
right: 0,
|
|
1530
|
+
marginTop: 0,
|
|
1531
|
+
zIndex: 1,
|
|
1532
|
+
},
|
|
1533
|
+
React.createElement(HelpBubble, { appMode }),
|
|
1534
|
+
)
|
|
1535
|
+
: null,
|
|
1536
|
+
);
|
|
910
1537
|
};
|
|
911
1538
|
|
|
1539
|
+
activateTerminalMode();
|
|
912
1540
|
render(React.createElement(App));
|