free-coding-models 0.3.19 → 0.3.22

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.
@@ -1,64 +1,200 @@
1
1
  /**
2
2
  * @file command-palette.js
3
3
  * @description Command palette registry and fuzzy search helpers for the main TUI.
4
+ * Now supports hierarchical categories with expandable/collapsible groups.
4
5
  *
5
6
  * @functions
6
- * → `buildCommandPaletteEntries` — builds the current command list with dynamic provider/tier context
7
+ * → `buildCommandPaletteTree` — builds the hierarchical command tree with categories and subcategories
8
+ * → `flattenCommandTree` — converts tree to flat list for filtering (respects expansion state)
7
9
  * → `fuzzyMatchCommand` — scores a query against one string and returns match positions
8
10
  * → `filterCommandPaletteEntries` — returns sorted command matches for a query
9
11
  *
10
- * @exports { COMMAND_CATEGORY_ORDER, buildCommandPaletteEntries, fuzzyMatchCommand, filterCommandPaletteEntries }
12
+ * @exports { buildCommandPaletteTree, flattenCommandTree, fuzzyMatchCommand, filterCommandPaletteEntries }
11
13
  *
12
14
  * @see src/key-handler.js
13
15
  * @see src/overlays.js
14
16
  */
15
17
 
16
- export const COMMAND_CATEGORY_ORDER = ['Filters', 'Sort', 'Pages', 'Actions']
17
-
18
- const COMMANDS = [
19
- // 📖 Filters
20
- { id: 'filter-tier-all', category: 'Filters', label: 'Filter tiers: all', shortcut: 'T', keywords: ['filter', 'tier', 'all'] },
21
- { id: 'filter-tier-splus', category: 'Filters', label: 'Filter tiers: S+', shortcut: null, keywords: ['filter', 'tier', 's+'] },
22
- { id: 'filter-tier-s', category: 'Filters', label: 'Filter tiers: S', shortcut: null, keywords: ['filter', 'tier', 's'] },
23
- { id: 'filter-tier-aplus', category: 'Filters', label: 'Filter tiers: A+', shortcut: null, keywords: ['filter', 'tier', 'a+'] },
24
- { id: 'filter-tier-a', category: 'Filters', label: 'Filter tiers: A', shortcut: null, keywords: ['filter', 'tier', 'a'] },
25
- { id: 'filter-tier-aminus', category: 'Filters', label: 'Filter tiers: A-', shortcut: null, keywords: ['filter', 'tier', 'a-'] },
26
- { id: 'filter-tier-bplus', category: 'Filters', label: 'Filter tiers: B+', shortcut: null, keywords: ['filter', 'tier', 'b+'] },
27
- { id: 'filter-tier-b', category: 'Filters', label: 'Filter tiers: B', shortcut: null, keywords: ['filter', 'tier', 'b'] },
28
- { id: 'filter-tier-c', category: 'Filters', label: 'Filter tiers: C', shortcut: null, keywords: ['filter', 'tier', 'c'] },
29
- { id: 'filter-provider-cycle', category: 'Filters', label: 'Filter provider: cycle', shortcut: 'D', keywords: ['filter', 'provider', 'origin'] },
30
- { id: 'filter-configured-toggle', category: 'Filters', label: 'Toggle configured-only models', shortcut: 'E', keywords: ['filter', 'configured', 'keys'] },
31
-
32
- // 📖 Sorting
33
- { id: 'sort-rank', category: 'Sort', label: 'Sort by rank', shortcut: 'R', keywords: ['sort', 'rank'] },
34
- { id: 'sort-tier', category: 'Sort', label: 'Sort by tier', shortcut: null, keywords: ['sort', 'tier'] },
35
- { id: 'sort-provider', category: 'Sort', label: 'Sort by provider', shortcut: 'O', keywords: ['sort', 'origin', 'provider'] },
36
- { id: 'sort-model', category: 'Sort', label: 'Sort by model name', shortcut: 'M', keywords: ['sort', 'model', 'name'] },
37
- { id: 'sort-latest-ping', category: 'Sort', label: 'Sort by latest ping', shortcut: 'L', keywords: ['sort', 'latest', 'ping'] },
38
- { id: 'sort-avg-ping', category: 'Sort', label: 'Sort by average ping', shortcut: 'A', keywords: ['sort', 'avg', 'average', 'ping'] },
39
- { id: 'sort-swe', category: 'Sort', label: 'Sort by SWE score', shortcut: 'S', keywords: ['sort', 'swe', 'score'] },
40
- { id: 'sort-ctx', category: 'Sort', label: 'Sort by context window', shortcut: 'C', keywords: ['sort', 'context', 'ctx'] },
41
- { id: 'sort-health', category: 'Sort', label: 'Sort by health', shortcut: 'H', keywords: ['sort', 'health', 'condition'] },
42
- { id: 'sort-verdict', category: 'Sort', label: 'Sort by verdict', shortcut: 'V', keywords: ['sort', 'verdict'] },
43
- { id: 'sort-stability', category: 'Sort', label: 'Sort by stability', shortcut: 'B', keywords: ['sort', 'stability'] },
44
- { id: 'sort-uptime', category: 'Sort', label: 'Sort by uptime', shortcut: 'U', keywords: ['sort', 'uptime'] },
45
-
46
- // 📖 Pages / overlays
47
- { id: 'open-settings', category: 'Pages', label: 'Open settings', shortcut: 'P', keywords: ['settings', 'config', 'api key'] },
48
- { id: 'open-help', category: 'Pages', label: 'Open help', shortcut: 'K', keywords: ['help', 'shortcuts', 'hotkeys'] },
49
- { id: 'open-changelog', category: 'Pages', label: 'Open changelog', shortcut: 'N', keywords: ['changelog', 'release'] },
50
- { id: 'open-feedback', category: 'Pages', label: 'Open feedback', shortcut: 'I', keywords: ['feedback', 'bug', 'request'] },
51
- { id: 'open-recommend', category: 'Pages', label: 'Open smart recommend', shortcut: 'Q', keywords: ['recommend', 'best model'] },
52
- { id: 'open-install-endpoints', category: 'Pages', label: 'Open install endpoints', shortcut: 'Y', keywords: ['install', 'endpoints', 'providers'] },
53
-
54
- // 📖 Actions
55
- { id: 'action-cycle-theme', category: 'Actions', label: 'Cycle theme', shortcut: 'G', keywords: ['theme', 'dark', 'light', 'auto'] },
56
- { id: 'action-cycle-tool-mode', category: 'Actions', label: 'Cycle tool mode', shortcut: 'Z', keywords: ['tool', 'mode', 'launcher'] },
57
- { id: 'action-cycle-ping-mode', category: 'Actions', label: 'Cycle ping mode', shortcut: 'W', keywords: ['ping', 'cadence', 'speed', 'slow'] },
58
- { id: 'action-toggle-favorite', category: 'Actions', label: 'Toggle favorite on selected model', shortcut: 'F', keywords: ['favorite', 'star'] },
59
- { id: 'action-reset-view', category: 'Actions', label: 'Reset view settings', shortcut: 'Shift+R', keywords: ['reset', 'view', 'sort', 'filters'] },
18
+ // 📖 Base command tree template (will be enhanced with dynamic model list)
19
+ const BASE_COMMAND_TREE = [
20
+ {
21
+ id: 'filters',
22
+ label: 'Filters',
23
+ icon: '🔍',
24
+ children: [
25
+ {
26
+ id: 'filter-tier',
27
+ label: 'Filter by tier',
28
+ icon: '📊',
29
+ children: [
30
+ { id: 'filter-tier-all', label: 'All tiers', tier: null, shortcut: 'T', description: 'Show all models', keywords: ['filter', 'tier', 'all'] },
31
+ { id: 'filter-tier-splus', label: 'S+ tier', tier: 'S+', description: 'Best coding models', keywords: ['filter', 'tier', 's+'] },
32
+ { id: 'filter-tier-s', label: 'S tier', tier: 'S', description: 'Excellent models', keywords: ['filter', 'tier', 's'] },
33
+ { id: 'filter-tier-aplus', label: 'A+ tier', tier: 'A+', description: 'Very good models', keywords: ['filter', 'tier', 'a+'] },
34
+ { id: 'filter-tier-a', label: 'A tier', tier: 'A', description: 'Good models', keywords: ['filter', 'tier', 'a'] },
35
+ { id: 'filter-tier-aminus', label: 'A- tier', tier: 'A-', description: 'Solid models', keywords: ['filter', 'tier', 'a-'] },
36
+ { id: 'filter-tier-bplus', label: 'B+ tier', tier: 'B+', description: 'Fair models', keywords: ['filter', 'tier', 'b+'] },
37
+ { id: 'filter-tier-b', label: 'B tier', tier: 'B', description: 'Basic models', keywords: ['filter', 'tier', 'b'] },
38
+ { id: 'filter-tier-c', label: 'C tier', tier: 'C', description: 'Limited models', keywords: ['filter', 'tier', 'c'] },
39
+ ]
40
+ },
41
+ {
42
+ id: 'filter-provider',
43
+ label: 'Filter by provider',
44
+ icon: '🏢',
45
+ children: [
46
+ { id: 'filter-provider-cycle', label: 'Cycle provider', shortcut: 'D', description: 'Switch between providers', keywords: ['filter', 'provider', 'origin'] },
47
+ { id: 'filter-provider-all', label: 'All providers', providerKey: null, description: 'Show all providers', keywords: ['filter', 'provider', 'all'] },
48
+ { id: 'filter-provider-nvidia', label: 'NVIDIA NIM', providerKey: 'nvidiaNim', description: 'NVIDIA models', keywords: ['filter', 'provider', 'nvidia', 'nim'] },
49
+ { id: 'filter-provider-groq', label: 'Groq', providerKey: 'groq', description: 'Groq models', keywords: ['filter', 'provider', 'groq'] },
50
+ { id: 'filter-provider-cerebras', label: 'Cerebras', providerKey: 'cerebras', description: 'Cerebras models', keywords: ['filter', 'provider', 'cerebras'] },
51
+ { id: 'filter-provider-sambanova', label: 'SambaNova', providerKey: 'sambanova', description: 'SambaNova models', keywords: ['filter', 'provider', 'sambanova'] },
52
+ { id: 'filter-provider-openrouter', label: 'OpenRouter', providerKey: 'openrouter', description: 'OpenRouter models', keywords: ['filter', 'provider', 'openrouter'] },
53
+ { id: 'filter-provider-together', label: 'Together AI', providerKey: 'together', description: 'Together models', keywords: ['filter', 'provider', 'together'] },
54
+ { id: 'filter-provider-deepinfra', label: 'DeepInfra', providerKey: 'deepinfra', description: 'DeepInfra models', keywords: ['filter', 'provider', 'deepinfra'] },
55
+ { id: 'filter-provider-fireworks', label: 'Fireworks AI', providerKey: 'fireworks', description: 'Fireworks models', keywords: ['filter', 'provider', 'fireworks'] },
56
+ { id: 'filter-provider-hyperbolic', label: 'Hyperbolic', providerKey: 'hyperbolic', description: 'Hyperbolic models', keywords: ['filter', 'provider', 'hyperbolic'] },
57
+ { id: 'filter-provider-google', label: 'Google AI', providerKey: 'google', description: 'Google models', keywords: ['filter', 'provider', 'google'] },
58
+ { id: 'filter-provider-huggingface', label: 'Hugging Face', providerKey: 'huggingface', description: 'Hugging Face models', keywords: ['filter', 'provider', 'huggingface'] },
59
+ ]
60
+ },
61
+ {
62
+ id: 'filter-model',
63
+ label: 'Filter by model',
64
+ icon: '🤖',
65
+ children: []
66
+ },
67
+ {
68
+ id: 'filter-other',
69
+ label: 'Other filters',
70
+ icon: '⚙️',
71
+ children: [
72
+ { id: 'filter-configured-toggle', label: 'Toggle configured-only', shortcut: 'E', description: 'Show only configured providers', keywords: ['filter', 'configured', 'keys'] },
73
+ ]
74
+ },
75
+ ]
76
+ },
77
+ {
78
+ id: 'sort',
79
+ label: 'Sort',
80
+ icon: '📶',
81
+ children: [
82
+ { id: 'sort-rank', label: 'Sort by rank', shortcut: 'R', description: 'Rank by SWE score', keywords: ['sort', 'rank'] },
83
+ { id: 'sort-tier', label: 'Sort by tier', description: 'Group by quality tier', keywords: ['sort', 'tier'] },
84
+ { id: 'sort-provider', label: 'Sort by provider', shortcut: 'O', description: 'Group by provider', keywords: ['sort', 'origin', 'provider'] },
85
+ { id: 'sort-model', label: 'Sort by model', shortcut: 'M', description: 'Alphabetical order', keywords: ['sort', 'model', 'name'] },
86
+ { id: 'sort-latest-ping', label: 'Sort by latest ping', shortcut: 'L', description: 'Recent response time', keywords: ['sort', 'latest', 'ping'] },
87
+ { id: 'sort-avg-ping', label: 'Sort by avg ping', shortcut: 'A', description: 'Average response time', keywords: ['sort', 'avg', 'average', 'ping'] },
88
+ { id: 'sort-swe', label: 'Sort by SWE score', shortcut: 'S', description: 'Coding ability score', keywords: ['sort', 'swe', 'score'] },
89
+ { id: 'sort-ctx', label: 'Sort by context', shortcut: 'C', description: 'Context window size', keywords: ['sort', 'context', 'ctx'] },
90
+ { id: 'sort-health', label: 'Sort by health', shortcut: 'H', description: 'Current model status', keywords: ['sort', 'health', 'condition'] },
91
+ { id: 'sort-verdict', label: 'Sort by verdict', shortcut: 'V', description: 'Overall assessment', keywords: ['sort', 'verdict'] },
92
+ { id: 'sort-stability', label: 'Sort by stability', shortcut: 'B', description: 'Reliability score', keywords: ['sort', 'stability'] },
93
+ { id: 'sort-uptime', label: 'Sort by uptime', shortcut: 'U', description: 'Success rate', keywords: ['sort', 'uptime'] },
94
+ ]
95
+ },
96
+ // 📖 Pages - directly at root level, not in submenu
97
+ { id: 'open-settings', label: 'Settings', shortcut: 'P', icon: '⚙️', type: 'page', description: 'API keys and preferences', keywords: ['settings', 'config', 'api key'] },
98
+ { id: 'open-help', label: 'Help', shortcut: 'K', icon: '❓', type: 'page', description: 'Show all shortcuts', keywords: ['help', 'shortcuts', 'hotkeys'] },
99
+ { id: 'open-changelog', label: 'Changelog', shortcut: 'N', icon: '📋', type: 'page', description: 'Version history', keywords: ['changelog', 'release'] },
100
+ { id: 'open-feedback', label: 'Feedback', shortcut: 'I', icon: '📝', type: 'page', description: 'Report bugs or requests', keywords: ['feedback', 'bug', 'request'] },
101
+ { id: 'open-recommend', label: 'Smart recommend', shortcut: 'Q', icon: '🎯', type: 'page', description: 'Find best model for task', keywords: ['recommend', 'best model'] },
102
+ { id: 'open-install-endpoints', label: 'Install endpoints', icon: '🔌', type: 'page', description: 'Install provider catalogs', keywords: ['install', 'endpoints', 'providers'] },
103
+ // 📖 Actions - directly at root level, not in submenu
104
+ { id: 'action-cycle-theme', label: 'Cycle theme', shortcut: 'G', icon: '🌗', type: 'action', description: 'Switch dark/light/auto', keywords: ['theme', 'dark', 'light', 'auto'] },
105
+ { id: 'action-cycle-tool-mode', label: 'Target Tool', shortcut: 'Z', icon: '🔄', type: 'action', description: 'Change target AI Coding CLI Tool.', keywords: ['tool', 'mode', 'launcher', 'target'] },
106
+ { id: 'action-cycle-ping-mode', label: 'Cycle ping mode', shortcut: 'W', icon: '⚡', type: 'action', description: 'Adjust ping speed', keywords: ['ping', 'cadence', 'speed', 'slow'] },
107
+ { id: 'action-toggle-favorite', label: 'Toggle favorite', shortcut: 'F', icon: '⭐', type: 'action', description: 'Pin to favorites', keywords: ['favorite', 'star'] },
108
+ { id: 'action-reset-view', label: 'Reset view', shortcut: 'Shift+R', icon: '🔄', type: 'action', description: 'Reset filters and sort', keywords: ['reset', 'view', 'sort', 'filters'] },
60
109
  ]
61
110
 
111
+ /**
112
+ * 📖 Build the command palette tree with dynamic model filters.
113
+ * @param {Array} visibleModels - Optional list of visible models to create model filter entries
114
+ * @returns {Array} The command tree with model filters added
115
+ */
116
+ export function buildCommandPaletteTree(visibleModels = []) {
117
+ // 📖 Clone the base tree
118
+ const tree = JSON.parse(JSON.stringify(BASE_COMMAND_TREE))
119
+
120
+ // 📖 Find the filter-model category and add dynamic model entries
121
+ const filterModelCategory = tree.find(cat => cat.id === 'filters')
122
+ ?.children.find(sub => sub.id === 'filter-model')
123
+
124
+ if (filterModelCategory && Array.isArray(visibleModels) && visibleModels.length > 0) {
125
+ // 📖 Add top 20 most-used or most relevant models
126
+ const topModels = visibleModels
127
+ .filter(m => !m.hidden && m.status !== 'noauth')
128
+ .slice(0, 20)
129
+
130
+ for (const model of topModels) {
131
+ filterModelCategory.children.push({
132
+ id: `filter-model-${model.providerKey}-${model.modelId}`,
133
+ label: model.label,
134
+ modelId: model.modelId,
135
+ providerKey: model.providerKey,
136
+ keywords: ['filter', 'model', model.label.toLowerCase(), model.modelId.toLowerCase()],
137
+ })
138
+ }
139
+ }
140
+
141
+ return tree
142
+ }
143
+
144
+ /**
145
+ * 📖 Flatten the command tree into a list, respecting which nodes are expanded.
146
+ * @param {Array} tree - The command tree
147
+ * @param {Set} expandedIds - Set of IDs that are expanded
148
+ * @returns {Array} Flat list with type markers ('category' | 'subcategory' | 'command' | 'page' | 'action')
149
+ */
150
+ export function flattenCommandTree(tree, expandedIds = new Set()) {
151
+ const result = []
152
+
153
+ function traverse(nodes, depth = 0) {
154
+ for (const node of nodes) {
155
+ // 📖 Check if this is a direct page/action (not in a submenu)
156
+ if (node.type === 'page' || node.type === 'action') {
157
+ result.push({
158
+ ...node,
159
+ type: node.type,
160
+ depth: 0,
161
+ hasChildren: false,
162
+ isExpanded: false,
163
+ })
164
+ continue
165
+ }
166
+
167
+ const isExpanded = expandedIds.has(node.id)
168
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0
169
+
170
+ if (hasChildren) {
171
+ result.push({
172
+ ...node,
173
+ type: depth === 0 ? 'category' : 'subcategory',
174
+ depth,
175
+ hasChildren,
176
+ isExpanded,
177
+ })
178
+
179
+ if (isExpanded) {
180
+ traverse(node.children, depth + 1)
181
+ }
182
+ } else {
183
+ result.push({
184
+ ...node,
185
+ type: 'command',
186
+ depth,
187
+ hasChildren: false,
188
+ isExpanded: false,
189
+ })
190
+ }
191
+ }
192
+ }
193
+
194
+ traverse(tree)
195
+ return result
196
+ }
197
+
62
198
  const ID_TO_TIER = {
63
199
  'filter-tier-all': null,
64
200
  'filter-tier-splus': 'S+',
@@ -71,11 +207,31 @@ const ID_TO_TIER = {
71
207
  'filter-tier-c': 'C',
72
208
  }
73
209
 
74
- export function buildCommandPaletteEntries() {
75
- return COMMANDS.map((entry) => ({
76
- ...entry,
77
- tierValue: Object.prototype.hasOwnProperty.call(ID_TO_TIER, entry.id) ? ID_TO_TIER[entry.id] : undefined,
78
- }))
210
+ /**
211
+ * 📖 Legacy function for backward compatibility - builds flat list from tree.
212
+ * 📖 Expands all categories so every command is searchable by fuzzyMatchCommand.
213
+ * @param {Array} visibleModels - Optional list of visible models for model filter entries
214
+ */
215
+ export function buildCommandPaletteEntries(visibleModels = []) {
216
+ const tree = buildCommandPaletteTree(visibleModels)
217
+ // 📖 Collect every node id that has children so flattenCommandTree traverses into them.
218
+ const allIds = new Set()
219
+ function collectIds(nodes) {
220
+ for (const n of nodes) {
221
+ allIds.add(n.id)
222
+ if (Array.isArray(n.children)) collectIds(n.children)
223
+ }
224
+ }
225
+ collectIds(tree)
226
+ const flat = flattenCommandTree(tree, allIds)
227
+ return flat.map((entry) => {
228
+ // 📖 Copy tier and providerKey properties to tierValue for backward compatibility
229
+ const result = { ...entry }
230
+ if (entry.tier !== undefined) {
231
+ result.tierValue = entry.tier
232
+ }
233
+ return result
234
+ })
79
235
  }
80
236
 
81
237
  /**
@@ -126,15 +282,20 @@ export function fuzzyMatchCommand(query, text) {
126
282
 
127
283
  /**
128
284
  * 📖 Filter and rank command palette entries by fuzzy score.
129
- * @param {Array<{ id: string, label: string, category: string, keywords?: string[] }>} entries
285
+ * Now handles hierarchical structure with expandable categories.
286
+ * @param {Array} flatEntries - Flattened command tree entries
130
287
  * @param {string} query
131
- * @returns {Array<{ id: string, label: string, category: string, shortcut?: string|null, keywords?: string[], score: number, matchPositions: number[] }>}
288
+ * @returns {Array} Sorted and filtered entries with match scores
132
289
  */
133
- export function filterCommandPaletteEntries(entries, query) {
290
+ export function filterCommandPaletteEntries(flatEntries, query) {
134
291
  const normalizedQuery = (query || '').trim()
292
+
293
+ if (!normalizedQuery) {
294
+ return flatEntries
295
+ }
135
296
 
136
297
  const ranked = []
137
- for (const entry of entries) {
298
+ for (const entry of flatEntries) {
138
299
  const labelMatch = fuzzyMatchCommand(normalizedQuery, entry.label)
139
300
  let bestScore = labelMatch.score
140
301
  let matchPositions = labelMatch.positions
@@ -145,7 +306,6 @@ export function filterCommandPaletteEntries(entries, query) {
145
306
  const keywordMatch = fuzzyMatchCommand(normalizedQuery, keyword)
146
307
  if (!keywordMatch.matched) continue
147
308
  matched = true
148
- // 📖 Keyword matches should rank below direct label matches.
149
309
  const keywordScore = Math.max(1, keywordMatch.score - 7)
150
310
  if (keywordScore > bestScore) {
151
311
  bestScore = keywordScore
@@ -158,11 +318,24 @@ export function filterCommandPaletteEntries(entries, query) {
158
318
  ranked.push({ ...entry, score: bestScore, matchPositions })
159
319
  }
160
320
 
321
+ // Auto-expand categories that contain matches
322
+ const result = []
323
+ const idsToExpand = new Set()
324
+
325
+ // First pass: mark all categories containing matched items
326
+ for (const entry of ranked) {
327
+ if (entry.type === 'command' && entry.matchPositions) {
328
+ // Find parent categories
329
+ let current = result.find(r => r.id === entry.id)
330
+ if (current) {
331
+ idsToExpand.add(entry.parentId)
332
+ }
333
+ }
334
+ }
335
+
161
336
  ranked.sort((a, b) => {
162
337
  if (b.score !== a.score) return b.score - a.score
163
- const aCat = COMMAND_CATEGORY_ORDER.indexOf(a.category)
164
- const bCat = COMMAND_CATEGORY_ORDER.indexOf(b.category)
165
- if (aCat !== bCat) return aCat - bCat
338
+ if (a.depth !== b.depth) return a.depth - b.depth
166
339
  return a.label.localeCompare(b.label)
167
340
  })
168
341
 
package/src/config.js CHANGED
@@ -209,7 +209,6 @@ function normalizeSettingsSection(settings) {
209
209
  return {
210
210
  ...safeSettings,
211
211
  hideUnconfiguredModels: typeof safeSettings.hideUnconfiguredModels === 'boolean' ? safeSettings.hideUnconfiguredModels : true,
212
- disableWidthsWarning: safeSettings.disableWidthsWarning === true,
213
212
  theme: ['dark', 'light', 'auto'].includes(safeSettings.theme) ? safeSettings.theme : 'auto',
214
213
  }
215
214
  }
@@ -230,7 +229,6 @@ function normalizeProfileSettings(settings) {
230
229
  return {
231
230
  ..._emptyProfileSettings(),
232
231
  ...safeSettings,
233
- disableWidthsWarning: safeSettings.disableWidthsWarning === true,
234
232
  theme: ['dark', 'light', 'auto'].includes(safeSettings.theme) ? safeSettings.theme : 'auto',
235
233
  }
236
234
  }
@@ -843,7 +841,6 @@ export function _emptyProfileSettings() {
843
841
  pingInterval: 10000, // 📖 default ms between pings in the steady "normal" mode
844
842
  hideUnconfiguredModels: true, // 📖 true = default to providers that are actually configured
845
843
  preferredToolMode: 'opencode', // 📖 remember the last Z-selected launcher across app restarts
846
- disableWidthsWarning: false, // 📖 Disable widths warning (default off)
847
844
  theme: 'auto', // 📖 'auto' follows the terminal/OS theme, override with 'dark' or 'light' if needed
848
845
  }
849
846
  }
package/src/constants.js CHANGED
@@ -17,6 +17,7 @@
17
17
  * - `CELL_W` is derived from `COL_MS` and used by `msCell` / `spinCell`.
18
18
  * - `TABLE_HEADER_LINES` + `TABLE_FOOTER_LINES` = `TABLE_FIXED_LINES` must stay in sync
19
19
  * with the actual number of lines rendered by `renderTable()` in bin/.
20
+ * - `WIDTH_WARNING_MIN_COLS` controls when the narrow-terminal startup warning appears.
20
21
  * - Overlay background colours (chalk.bgRgb) make each overlay panel visually distinct.
21
22
  *
22
23
  * @functions
@@ -30,6 +31,7 @@
30
31
  * FRAMES, TIER_CYCLE,
31
32
  * SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, LOG_OVERLAY_BG,
32
33
  * OVERLAY_PANEL_WIDTH,
34
+ * WIDTH_WARNING_MIN_COLS,
33
35
  * TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES,
34
36
  * msCell, spinCell
35
37
  *
@@ -83,6 +85,9 @@ export const LOG_OVERLAY_BG = chalk.bgRgb(0, 0, 0) // 📖 Dark blue-gree
83
85
  // 📖 tint fills the panel consistently regardless of content length.
84
86
  export const OVERLAY_PANEL_WIDTH = 116
85
87
 
88
+ // 📖 Narrow-terminal warning appears only below this width.
89
+ export const WIDTH_WARNING_MIN_COLS = 80
90
+
86
91
  // 📖 Table row-budget constants — must stay in sync with renderTable()'s actual output.
87
92
  // 📖 If this drifts, model rows overflow and can push the title row out of view.
88
93
  export const TABLE_HEADER_LINES = 4 // 📖 title, spacer, column headers, separator