codex-configurator 0.2.4 → 0.2.5

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/src/constants.js CHANGED
@@ -1,5 +1,12 @@
1
- export const CONTROL_HINT = '↑/↓ move • PgUp/PgDn page • Home/End jump • Enter: open section or edit • /: filter • Del: unset value • ←/Backspace: back • r: reload • q: quit';
2
- export const EDIT_CONTROL_HINT = '↑/↓ choose • PgUp/PgDn page • Home/End jump • Enter: saveEsc/Backspace/←: cancel • Del: delete char (text input) r: reload • q: quit';
3
- export const FILTER_CONTROL_HINT = 'Type filter • Enter/Esc: done • Del/Backspace: delete • Ctrl+U: clear';
1
+ export const CONTROL_HINT =
2
+ '↑/↓ move • PgUp/PgDn page • Home/End jump • Enter open/editBackspace/Del/Ctrl+D unset value parent';
3
+ export const FILE_SWITCH_HINT =
4
+ '↑/↓ choose • PgUp/PgDn page • Home/End jump • Enter switch file • Esc/Backspace/← cancel';
5
+ export const EDIT_CONTROL_HINT =
6
+ '↑/↓ choose • PgUp/PgDn page • Home/End jump • Enter save • Esc/Backspace/← cancel • Del delete char (text input)';
7
+ export const FILTER_CONTROL_HINT =
8
+ 'Type filter • Enter/Esc: done • Del/Backspace: delete • Ctrl+U: clear';
9
+ export const COMMAND_HINT = 'Type : to see commands';
10
+ export const COMMAND_INPUT_HINT = '';
4
11
  export const BRAND = 'CODEX CONFIGURATOR';
5
12
  export const CONFIG_TAGS = ['Node.js', 'React', 'Ink', 'TOML'];
@@ -0,0 +1,118 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ export const MAIN_CONFIG_FILE_ID = 'main';
5
+
6
+ const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]';
7
+
8
+ const expandHomePath = (value, homeDir = os.homedir()) => {
9
+ const text = String(value || '').trim();
10
+ if (!text) {
11
+ return '';
12
+ }
13
+
14
+ if (text === '~') {
15
+ return homeDir;
16
+ }
17
+
18
+ if (text.startsWith('~/') || text.startsWith('~\\')) {
19
+ return path.join(homeDir, text.slice(2));
20
+ }
21
+
22
+ return text;
23
+ };
24
+
25
+ const normalizeConfiguredPath = (value, baseDir) => {
26
+ const expanded = expandHomePath(value);
27
+ if (!expanded) {
28
+ return '';
29
+ }
30
+
31
+ return path.isAbsolute(expanded) ? expanded : path.resolve(baseDir, expanded);
32
+ };
33
+
34
+ const buildMainConfigEntry = (mainConfigPath) => ({
35
+ id: MAIN_CONFIG_FILE_ID,
36
+ kind: 'main',
37
+ label: 'main config',
38
+ path: mainConfigPath,
39
+ agentNames: [],
40
+ });
41
+
42
+ const normalizeAgentName = (agentName, fallbackIndex) => {
43
+ const trimmed = String(agentName || '').trim();
44
+ if (trimmed) {
45
+ return trimmed;
46
+ }
47
+
48
+ return `agent-${fallbackIndex}`;
49
+ };
50
+
51
+ export const buildAgentConfigFileEntries = (mainConfigData, mainConfigPath) => {
52
+ const agentNodes = isPlainObject(mainConfigData) && isPlainObject(mainConfigData.agents)
53
+ ? mainConfigData.agents
54
+ : {};
55
+ const normalizedMainConfigPath = String(mainConfigPath || '').trim() || '';
56
+ const baseDir = path.dirname(normalizedMainConfigPath || process.cwd());
57
+ const byPath = new Map();
58
+ let index = 0;
59
+
60
+ Object.entries(agentNodes).forEach(([agentName, definition]) => {
61
+ if (!isPlainObject(definition)) {
62
+ return;
63
+ }
64
+
65
+ const candidate = normalizeConfiguredPath(definition.config_file, baseDir);
66
+ if (!candidate || !definition.config_file) {
67
+ return;
68
+ }
69
+
70
+ const resolvedPath = path.resolve(candidate);
71
+ if (!path.isAbsolute(resolvedPath)) {
72
+ return;
73
+ }
74
+
75
+ const safeName = normalizeAgentName(agentName, ++index);
76
+
77
+ const existing = byPath.get(resolvedPath);
78
+ if (existing) {
79
+ existing.agentNames.push(safeName);
80
+ return;
81
+ }
82
+
83
+ byPath.set(resolvedPath, {
84
+ id: `agent:${resolvedPath}`,
85
+ kind: 'agent',
86
+ label: `agents.${safeName}.config_file`,
87
+ path: resolvedPath,
88
+ agentNames: [safeName],
89
+ });
90
+ });
91
+
92
+ const entries = [];
93
+ byPath.forEach((entry) => {
94
+ const allAgents = [...new Set(entry.agentNames)].sort((a, b) => a.localeCompare(b));
95
+ const label = allAgents.length === 1
96
+ ? `agent: ${allAgents[0]} (${entry.path})`
97
+ : `agents: ${allAgents.join(', ')} (${entry.path})`;
98
+
99
+ entries.push({
100
+ ...entry,
101
+ label,
102
+ agentNames: allAgents,
103
+ });
104
+ });
105
+
106
+ return entries.sort((left, right) => left.label.localeCompare(right.label));
107
+ };
108
+
109
+ export const buildConfigFileCatalog = (mainConfigSnapshot = { path: '', data: {} }) => {
110
+ const normalizedMainPath = path.resolve(
111
+ String(mainConfigSnapshot.path || '').trim() || path.resolve(process.cwd(), '.codex', 'config.toml')
112
+ );
113
+ const mainEntry = buildMainConfigEntry(normalizedMainPath);
114
+
115
+ const agentEntries = buildAgentConfigFileEntries(mainConfigSnapshot.data || {}, normalizedMainPath);
116
+
117
+ return [mainEntry, ...agentEntries];
118
+ };
@@ -1,25 +1,69 @@
1
- export const isBackspaceKey = (input, key) =>
2
- key.backspace === true || key.name === 'backspace' || input === '\b' || input === '\u007f';
3
-
4
- export const isDeleteKey = (input, key) =>
5
- key.delete === true || key.name === 'delete' || input === '\u001b[3~';
6
-
7
- export const isPageUpKey = (input, key) =>
8
- key.pageUp === true || key.name === 'pageup' || key.name === 'page-up' || input === '\u001b[5~';
9
-
10
- export const isPageDownKey = (input, key) =>
11
- key.pageDown === true || key.name === 'pagedown' || key.name === 'page-down' || input === '\u001b[6~';
12
-
13
- export const isHomeKey = (input, key) =>
14
- key.home === true ||
15
- key.name === 'home' ||
16
- input === '\u001b[H' ||
17
- input === '\u001b[1~' ||
18
- input === '\u001bOH';
19
-
20
- export const isEndKey = (input, key) =>
21
- key.end === true ||
22
- key.name === 'end' ||
23
- input === '\u001b[F' ||
24
- input === '\u001b[4~' ||
25
- input === '\u001bOF';
1
+ const readKeyName = (key) => String(key?.name || '').toLowerCase();
2
+ const readKeySequence = (input, key) => {
3
+ if (typeof input === 'string' && input.length > 0) {
4
+ return input;
5
+ }
6
+
7
+ return typeof key?.sequence === 'string' ? key.sequence : '';
8
+ };
9
+
10
+ const isBackspaceSequence = (sequence) =>
11
+ sequence === '\b' || sequence === '\u007f';
12
+
13
+ const isForwardDeleteSequence = (sequence) =>
14
+ sequence === '\u001b[3~' ||
15
+ (sequence.startsWith('\u001b[3;') && sequence.endsWith('~'));
16
+
17
+ export const isBackspaceKey = (input, key) => {
18
+ const name = readKeyName(key);
19
+ const sequence = readKeySequence(input, key);
20
+ const ambiguousDeleteSignal =
21
+ key?.delete === true &&
22
+ !isForwardDeleteSequence(sequence) &&
23
+ (sequence === '' || isBackspaceSequence(sequence));
24
+
25
+ return key.backspace === true
26
+ || name === 'backspace'
27
+ || isBackspaceSequence(sequence)
28
+ || ambiguousDeleteSignal;
29
+ };
30
+
31
+ export const isDeleteKey = (input, key) => {
32
+ const name = readKeyName(key);
33
+ const sequence = readKeySequence(input, key);
34
+
35
+ return !isBackspaceKey(input, key) &&
36
+ (name === 'delete' || isForwardDeleteSequence(sequence));
37
+ };
38
+
39
+ export const isPageUpKey = (input, key) => {
40
+ const name = readKeyName(key);
41
+ const sequence = readKeySequence(input, key);
42
+ return key.pageUp === true || name === 'pageup' || name === 'page-up' || sequence === '\u001b[5~';
43
+ };
44
+
45
+ export const isPageDownKey = (input, key) => {
46
+ const name = readKeyName(key);
47
+ const sequence = readKeySequence(input, key);
48
+ return key.pageDown === true || name === 'pagedown' || name === 'page-down' || sequence === '\u001b[6~';
49
+ };
50
+
51
+ export const isHomeKey = (input, key) => {
52
+ const name = readKeyName(key);
53
+ const sequence = readKeySequence(input, key);
54
+ return key.home === true ||
55
+ name === 'home' ||
56
+ sequence === '\u001b[H' ||
57
+ sequence === '\u001b[1~' ||
58
+ sequence === '\u001bOH';
59
+ };
60
+
61
+ export const isEndKey = (input, key) => {
62
+ const name = readKeyName(key);
63
+ const sequence = readKeySequence(input, key);
64
+ return key.end === true ||
65
+ name === 'end' ||
66
+ sequence === '\u001b[F' ||
67
+ sequence === '\u001b[4~' ||
68
+ sequence === '\u001bOF';
69
+ };
package/src/layout.js CHANGED
@@ -2,19 +2,84 @@ export const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
2
2
 
3
3
  export const pathToKey = (segments) => JSON.stringify(segments);
4
4
 
5
- export const computePaneWidths = (terminalWidth, rows) => {
6
- const available = Math.max(40, terminalWidth - 2);
7
- const contentRows = rows.length === 0 ? [] : rows;
8
- const longestRow = contentRows.reduce(
9
- (max, row) => Math.max(max, String(row.label).length + 8),
10
- 26
11
- );
12
- const minLeftWidth = 30;
13
- const minRightWidth = 24;
14
-
15
- const leftNeed = Math.max(minLeftWidth, longestRow);
16
- const leftWidth = clamp(leftNeed, minLeftWidth, available - minRightWidth - 2);
17
- const rightWidth = available - leftWidth - 2;
18
-
19
- return { leftWidth, rightWidth };
5
+ const MIN_LEFT_WIDTH = 32;
6
+ const MIN_RIGHT_WIDTH = 24;
7
+ const SPLIT_GAP = 2;
8
+ const LEFT_PANE_WIDTH_RATIO = 0.3;
9
+ const MIN_TERMINAL_WIDTH = 58;
10
+ const SHELL_VERTICAL_PADDING = 0;
11
+ const COMMAND_BAR_ROWS_BROWSE = 3; // 1 text + top/bottom borders
12
+ const COMMAND_BAR_ROWS_COMMAND = 4; // 2 text + top/bottom borders
13
+ const STATUS_BAR_ROWS = 1; // 1 content (no trailing margin needed logically now)
14
+ const HEADER_MARGIN_BOTTOM = 0;
15
+ const NON_INTERACTIVE_POST_LIST_ROWS = 1;
16
+ const NAVIGATOR_FRAME_ROWS = 4;
17
+
18
+ export const formatActiveFileSummary = (activeConfigFile) => {
19
+ const label = String(activeConfigFile?.label || 'unknown');
20
+ const filePath = String(activeConfigFile?.path || 'path unavailable');
21
+ return label.includes(filePath) ? label : `${label} (${filePath})`;
22
+ };
23
+
24
+ export const computeHeaderRows = () => {
25
+ return {
26
+ isCompact: true,
27
+ rows: 1,
28
+ };
29
+ };
30
+
31
+ export const computeChromeRows = ({ isInteractive, isCommandMode }) => {
32
+ const header = computeHeaderRows();
33
+ const gaps = isInteractive ? 1 : 0; // We reduced the margin gaps since components are hugging each other naturally in TUI
34
+ const commandRows = isInteractive
35
+ ? isCommandMode
36
+ ? COMMAND_BAR_ROWS_COMMAND
37
+ : COMMAND_BAR_ROWS_BROWSE
38
+ : 0;
39
+ const statusRows = isInteractive ? STATUS_BAR_ROWS : 0;
40
+ const postListRows = isInteractive ? 0 : NON_INTERACTIVE_POST_LIST_ROWS;
41
+
42
+ return (
43
+ SHELL_VERTICAL_PADDING +
44
+ HEADER_MARGIN_BOTTOM +
45
+ NAVIGATOR_FRAME_ROWS +
46
+ header.rows +
47
+ gaps +
48
+ commandRows +
49
+ statusRows +
50
+ postListRows
51
+ );
52
+ };
53
+
54
+ export const computeListViewportRows = ({
55
+ terminalHeight,
56
+ isInteractive = true,
57
+ isCommandMode = false,
58
+ chromeRows,
59
+ extraChromeRows = 0,
60
+ }) => {
61
+ const resolvedChromeRows =
62
+ chromeRows ||
63
+ computeChromeRows({
64
+ isInteractive,
65
+ isCommandMode,
66
+ });
67
+ const availableRows =
68
+ (terminalHeight || 24) -
69
+ resolvedChromeRows -
70
+ Math.max(0, extraChromeRows);
71
+
72
+ return Math.max(1, availableRows);
73
+ };
74
+
75
+ export const computePaneWidths = (terminalWidth) => {
76
+ const available = Math.max(MIN_TERMINAL_WIDTH, terminalWidth - 2);
77
+ const leftWidth = clamp(
78
+ Math.floor(available * LEFT_PANE_WIDTH_RATIO),
79
+ MIN_LEFT_WIDTH,
80
+ available - MIN_RIGHT_WIDTH - SPLIT_GAP,
81
+ );
82
+ const rightWidth = available - leftWidth - SPLIT_GAP;
83
+
84
+ return { leftWidth, rightWidth };
20
85
  };