codex-configurator 0.2.2 → 0.2.4

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/configHelp.js CHANGED
@@ -2,7 +2,12 @@ import {
2
2
  getConfigFeatureDefinition,
3
3
  getConfigFeatureDefinitionOrFallback,
4
4
  } from './configFeatures.js';
5
- import { getReferenceOptionForPath } from './configReference.js';
5
+ import {
6
+ getReferenceOptionForPath,
7
+ getReferenceCustomIdPlaceholder,
8
+ getReferenceDescendantOptions,
9
+ getReferenceTableDefinitions,
10
+ } from './configReference.js';
6
11
 
7
12
  const CONFIG_VALUE_OPTIONS = {
8
13
  model: [
@@ -16,12 +21,6 @@ const CONFIG_VALUE_OPTIONS = {
16
21
  };
17
22
 
18
23
  const CONFIG_PATH_EXPLANATIONS = [
19
- {
20
- path: ['tools', 'web_search'],
21
- short: 'Deprecated legacy web search flag.',
22
- usage: 'Use the top-level web_search setting instead.',
23
- deprecation: 'tools.web_search is deprecated; use the top-level web_search setting instead.',
24
- },
25
24
  {
26
25
  path: ['projects', '*', 'trust_level'],
27
26
  short: 'Controls how much trust this project gets for command execution.',
@@ -54,6 +53,24 @@ const CONFIG_OPTION_EXPLANATIONS = {
54
53
  untrusted: 'Limits risky actions and prompts more often.',
55
54
  },
56
55
  };
56
+ const SECTION_PURPOSE_OVERRIDES = {
57
+ agents: 'Named agent definitions and per-agent configuration file references.',
58
+ apps: 'Per-app enablement rules and tool-level approval controls.',
59
+ features: 'Feature flags for optional and experimental Codex behavior.',
60
+ feedback: 'Feedback submission settings for Codex surfaces.',
61
+ history: 'Session history retention policy and on-disk size limits.',
62
+ mcp_servers: 'MCP server definitions, transport settings, and authentication configuration.',
63
+ model_providers: 'Model provider definitions, API endpoints, and credential settings.',
64
+ notice: 'Visibility toggles for startup and migration notices.',
65
+ otel: 'OpenTelemetry exporter configuration for telemetry and traces.',
66
+ profiles: 'Named profile overrides you can select per session.',
67
+ projects: 'Project/worktree trust settings scoped by filesystem path.',
68
+ sandbox_workspace_write: 'Workspace-write sandbox behavior, writable roots, and network access rules.',
69
+ shell_environment_policy: 'Shell environment inheritance and variable override policy.',
70
+ skills: 'Skill discovery and loading controls.',
71
+ tools: 'Tool-related configuration, including legacy compatibility flags.',
72
+ tui: 'Terminal UI behavior, notifications, and presentation settings.',
73
+ };
57
74
 
58
75
  const makePathSegments = (segments, key) => {
59
76
  const normalizedSegments = Array.isArray(segments)
@@ -78,6 +95,73 @@ const getContextEntry = (segments, key, candidates) => {
78
95
 
79
96
  const getReferenceEntry = (segments, key) => getReferenceOptionForPath(makePathSegments(segments, key));
80
97
 
98
+ const formatPlaceholderLabel = (placeholder) => {
99
+ const cleaned = String(placeholder || '')
100
+ .replace(/^</, '')
101
+ .replace(/>$/, '');
102
+
103
+ return cleaned || 'id';
104
+ };
105
+
106
+ const toFirstSentence = (text) => {
107
+ const normalized = String(text || '').replace(/\s+/g, ' ').trim();
108
+ if (!normalized) {
109
+ return '';
110
+ }
111
+
112
+ const periodIndex = normalized.indexOf('. ');
113
+ if (periodIndex < 0) {
114
+ return normalized;
115
+ }
116
+
117
+ return normalized.slice(0, periodIndex + 1);
118
+ };
119
+
120
+ const getSectionPurposeDescription = (descendantOptions) => {
121
+ for (const option of descendantOptions) {
122
+ const firstSentence = toFirstSentence(option?.description);
123
+ if (firstSentence) {
124
+ return firstSentence;
125
+ }
126
+ }
127
+
128
+ return '';
129
+ };
130
+
131
+ const buildInferredSectionHelp = (segments, key) => {
132
+ const sectionPath = makePathSegments(segments, key);
133
+ const childDefinitions = getReferenceTableDefinitions(sectionPath);
134
+ const descendantOptions = getReferenceDescendantOptions(sectionPath);
135
+ const customPlaceholder = getReferenceCustomIdPlaceholder(sectionPath);
136
+ const parentCustomPlaceholder = getReferenceCustomIdPlaceholder(sectionPath.slice(0, -1));
137
+
138
+ if (childDefinitions.length === 0 && descendantOptions.length === 0 && !customPlaceholder) {
139
+ return null;
140
+ }
141
+
142
+ const rootOverride = sectionPath.length === 1
143
+ ? SECTION_PURPOSE_OVERRIDES[sectionPath[0]]
144
+ : null;
145
+ const customLabel = customPlaceholder ? formatPlaceholderLabel(customPlaceholder) : '';
146
+ const bestPurposeDescription = getSectionPurposeDescription(descendantOptions);
147
+ const dynamicEntryLabel = parentCustomPlaceholder
148
+ ? formatPlaceholderLabel(parentCustomPlaceholder)
149
+ : '';
150
+ const short = rootOverride
151
+ || (dynamicEntryLabel
152
+ ? `Configuration for this ${dynamicEntryLabel} entry.`
153
+ : '')
154
+ || bestPurposeDescription
155
+ || (customLabel
156
+ ? `Section for custom ${customLabel} entries.`
157
+ : 'Section with related settings.');
158
+
159
+ return {
160
+ short,
161
+ usage: null,
162
+ };
163
+ };
164
+
81
165
  const getReferenceUsage = (entry) => {
82
166
  if (entry.deprecated) {
83
167
  return 'This option is deprecated in the official configuration reference.';
@@ -92,7 +176,7 @@ const getReferenceUsage = (entry) => {
92
176
  }
93
177
 
94
178
  if (entry.type === 'table') {
95
- return 'This section groups related settings.';
179
+ return 'Press Enter to open this section and edit nested settings.';
96
180
  }
97
181
 
98
182
  if (entry.type.startsWith('array<')) {
@@ -144,6 +228,11 @@ export const getConfigHelp = (segments, key) => {
144
228
  return referenceHelp;
145
229
  }
146
230
 
231
+ const inferredSectionHelp = buildInferredSectionHelp(segments, key);
232
+ if (inferredSectionHelp) {
233
+ return inferredSectionHelp;
234
+ }
235
+
147
236
  return null;
148
237
  };
149
238
 
@@ -409,8 +409,7 @@ const formatRowLabel = (key, kind, value) =>
409
409
  : `${key} = ${previewValue(value)}`;
410
410
 
411
411
  const isPathDeprecated = (pathSegments, key) =>
412
- Boolean(getReferenceOptionForPath([...pathSegments, String(key)])?.deprecated) ||
413
- isToolsWebSearchDeprecated(pathSegments, key);
412
+ Boolean(getReferenceOptionForPath([...pathSegments, String(key)])?.deprecated);
414
413
 
415
414
  const sortRowsAlphabetically = (rows) =>
416
415
  [...rows].sort((left, right) => String(left.key).localeCompare(String(right.key)));
@@ -523,9 +522,6 @@ const buildRootRows = (node) => buildDefinedRows(node, getReferenceRootDefinitio
523
522
  const getTableDefinitions = (pathSegments) =>
524
523
  Array.isArray(pathSegments) ? getReferenceTableDefinitions(pathSegments) : [];
525
524
 
526
- const isToolsWebSearchDeprecated = (pathSegments, key) =>
527
- pathSegments[pathSegments.length - 1] === 'tools' && key === 'web_search';
528
-
529
525
  export const getNodeAtPath = (root, segments) => {
530
526
  let current = root;
531
527
 
@@ -5,7 +5,6 @@ const CONFIG_REFERENCE_DATA = require('./reference/config-reference.json');
5
5
 
6
6
  const DOCUMENT_ID = 'config.toml';
7
7
  const PLACEHOLDER_SEGMENT = /^<[^>]+>$/;
8
- const NON_DEPRECATED_KEYS = new Set(['approval_policy']);
9
8
  const KIND_PRIORITY = {
10
9
  value: 1,
11
10
  array: 2,
@@ -93,7 +92,7 @@ const referenceOptions = Array.isArray(configDocument?.options)
93
92
  ? option.enum_values.map((value) => String(value))
94
93
  : [],
95
94
  description: String(option?.description || ''),
96
- deprecated: option?.deprecated === true && !NON_DEPRECATED_KEYS.has(key),
95
+ deprecated: option?.deprecated === true,
97
96
  };
98
97
  })
99
98
  : [];
@@ -218,3 +217,15 @@ export const getReferenceCustomIdPlaceholder = (pathSegments = []) => {
218
217
  const [firstMatch] = [...placeholders];
219
218
  return firstMatch || null;
220
219
  };
220
+
221
+ export const getReferenceDescendantOptions = (pathSegments = []) => {
222
+ const normalizedPath = normalizeSegments(pathSegments);
223
+
224
+ return referenceOptions
225
+ .filter(
226
+ (option) =>
227
+ pathPrefixMatches(option.keyPath, normalizedPath) &&
228
+ option.keyPath.length > normalizedPath.length
229
+ )
230
+ .sort((left, right) => left.keyPath.length - right.keyPath.length || left.key.localeCompare(right.key));
231
+ };
package/src/constants.js CHANGED
@@ -1,4 +1,5 @@
1
- export const CONTROL_HINT = '↑/↓ move • PgUp/PgDn page • Home/End jump • Enter: open section or edit • Del: unset value • ←/Backspace: back • r: reload • q: quit';
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
2
  export const EDIT_CONTROL_HINT = '↑/↓ choose • PgUp/PgDn page • Home/End jump • Enter: save • Esc/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';
3
4
  export const BRAND = 'CODEX CONFIGURATOR';
4
5
  export const CONFIG_TAGS = ['Node.js', 'React', 'Ink', 'TOML'];
@@ -0,0 +1,39 @@
1
+ const normalizeText = (value) => String(value || '').toLowerCase();
2
+
3
+ export const isFuzzyMatch = (query, text) => {
4
+ const normalizedQuery = normalizeText(query).trim();
5
+ if (!normalizedQuery) {
6
+ return true;
7
+ }
8
+
9
+ const normalizedText = normalizeText(text);
10
+ let queryIndex = 0;
11
+
12
+ for (let textIndex = 0; textIndex < normalizedText.length; textIndex += 1) {
13
+ if (normalizedText[textIndex] === normalizedQuery[queryIndex]) {
14
+ queryIndex += 1;
15
+ if (queryIndex >= normalizedQuery.length) {
16
+ return true;
17
+ }
18
+ }
19
+ }
20
+
21
+ return false;
22
+ };
23
+
24
+ const rowSearchableTexts = (row) => [
25
+ row?.label,
26
+ row?.key,
27
+ row?.preview,
28
+ ];
29
+
30
+ export const filterRowsByQuery = (rows, query) => {
31
+ const normalizedQuery = String(query || '').trim();
32
+ if (!normalizedQuery) {
33
+ return rows;
34
+ }
35
+
36
+ return rows.filter((row) =>
37
+ rowSearchableTexts(row).some((value) => isFuzzyMatch(normalizedQuery, value))
38
+ );
39
+ };