zames_pro 2.82.141 → 2.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +19 -0
- package/dist/attachments.js +4 -7
- package/dist/backlog.js +2 -2
- package/dist/browser-pure.js +2 -2
- package/dist/commands.js +2 -2
- package/dist/compact.js +1 -1
- package/dist/config.js +10 -1
- package/dist/glm-browser.js +1 -1
- package/dist/glm-net-capture.js +1 -1
- package/dist/glm-ui.js +0 -20
- package/dist/hooks.js +1 -1
- package/dist/i18n/config.js +4 -0
- package/dist/index.js +24 -1
- package/dist/mcp.js +2 -2
- package/dist/qwen-browser.js +1 -1
- package/dist/qwen-ui.js +0 -2
- package/dist/run-task.js +1 -1
- package/dist/scheduler.js +1 -1
- package/dist/sessions.js +1 -1
- package/dist/spinner.js +1 -1
- package/dist/statusline.js +2 -2
- package/dist/subagent.js +3 -3
- package/dist/system-prompt.js +1 -1
- package/dist/theme.js +239 -37
- package/dist/tui/AskQuestion.js +2 -2
- package/dist/tui/ConfigMenu.js +7 -1
- package/dist/tui/FullScreenApp.js +11 -18
- package/dist/tui/InputLine.js +5 -4
- package/dist/tui/LogView.js +1 -1
- package/dist/tui/SelectList.js +1 -1
- package/dist/tui/StatusBar.js +10 -9
- package/dist/tui/TuiEditor.js +9 -0
- package/dist/tui/edit.js +1 -1
- package/dist/tui/layout.js +1 -1
- package/dist/tui/store.js +1 -0
- package/dist/tui/ui-colors.js +37 -0
- package/dist/tui/widgets.js +6 -41
- package/dist/web.js +1 -1
- package/dist/width.js +1 -1
- package/llms-full.txt +9 -0
- package/package.json +1 -1
- package/dist/code-blocks.js +0 -54
package/dist/theme.js
CHANGED
|
@@ -9,44 +9,246 @@ import { contentWidth } from './width.js';
|
|
|
9
9
|
if (process.env.NO_COLOR) {
|
|
10
10
|
chalk.level = 0;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
12
|
+
export const PALETTES = {
|
|
13
|
+
// zames — the original calm palette (Tokyo Night / Nord spirit): muted,
|
|
14
|
+
// slightly faded tones, comfortable for long answers.
|
|
15
|
+
zames: {
|
|
16
|
+
bg: '#23272e',
|
|
17
|
+
user: '#8fd4f0',
|
|
18
|
+
userBar: '#4a8fb5',
|
|
19
|
+
turnRule: '#3a3f4b',
|
|
20
|
+
turnLabel: '#5b616e',
|
|
21
|
+
prompt: '#c8b06a',
|
|
22
|
+
dir: '#7fc4f0',
|
|
23
|
+
assistant: '#cfc9b0',
|
|
24
|
+
tool: '#c6a97e',
|
|
25
|
+
toolResult: '#8a9bb5',
|
|
26
|
+
system: '#808896',
|
|
27
|
+
dim: '#5b616e',
|
|
28
|
+
taskSummary: '#8fa3c8',
|
|
29
|
+
warn: '#c9a86a',
|
|
30
|
+
error: '#c98a80',
|
|
31
|
+
success: '#a9c08c',
|
|
32
|
+
brown: '#a1723f',
|
|
33
|
+
toggleOn: '#6fd0b0',
|
|
34
|
+
},
|
|
35
|
+
// One Dark — Atom's default theme, the most recognized "modern editor" look.
|
|
36
|
+
'one-dark': {
|
|
37
|
+
bg: '#282c34',
|
|
38
|
+
user: '#61afef',
|
|
39
|
+
userBar: '#3e6f9e',
|
|
40
|
+
turnRule: '#3b4048',
|
|
41
|
+
turnLabel: '#5c6370',
|
|
42
|
+
prompt: '#e5c07b',
|
|
43
|
+
dir: '#61afef',
|
|
44
|
+
assistant: '#abb2bf',
|
|
45
|
+
tool: '#d19a66',
|
|
46
|
+
toolResult: '#7f8b9e',
|
|
47
|
+
system: '#828997',
|
|
48
|
+
dim: '#5c6370',
|
|
49
|
+
taskSummary: '#56b6c2',
|
|
50
|
+
warn: '#e5c07b',
|
|
51
|
+
error: '#e06c75',
|
|
52
|
+
success: '#98c379',
|
|
53
|
+
brown: '#d19a66',
|
|
54
|
+
toggleOn: '#56b6c2',
|
|
55
|
+
},
|
|
56
|
+
// Dracula — high-contrast purple/pink, extremely popular.
|
|
57
|
+
dracula: {
|
|
58
|
+
bg: '#282a36',
|
|
59
|
+
user: '#8be9fd',
|
|
60
|
+
userBar: '#4f8fa6',
|
|
61
|
+
turnRule: '#3d4152',
|
|
62
|
+
turnLabel: '#6272a4',
|
|
63
|
+
prompt: '#f1fa8c',
|
|
64
|
+
dir: '#8be9fd',
|
|
65
|
+
assistant: '#f8f8f2',
|
|
66
|
+
tool: '#ffb86c',
|
|
67
|
+
toolResult: '#b0b8d8',
|
|
68
|
+
system: '#9aa2c0',
|
|
69
|
+
dim: '#6272a4',
|
|
70
|
+
taskSummary: '#8be9fd',
|
|
71
|
+
warn: '#ffb86c',
|
|
72
|
+
error: '#ff5555',
|
|
73
|
+
success: '#50fa7b',
|
|
74
|
+
brown: '#bd93f9',
|
|
75
|
+
toggleOn: '#50fa7b',
|
|
76
|
+
},
|
|
77
|
+
// Gruvbox Dark — warm retro earth tones.
|
|
78
|
+
gruvbox: {
|
|
79
|
+
bg: '#282828',
|
|
80
|
+
user: '#83a598',
|
|
81
|
+
userBar: '#5a7a6e',
|
|
82
|
+
turnRule: '#3c3836',
|
|
83
|
+
turnLabel: '#665c54',
|
|
84
|
+
prompt: '#fabd2f',
|
|
85
|
+
dir: '#83a598',
|
|
86
|
+
assistant: '#ebdbb2',
|
|
87
|
+
tool: '#fe8019',
|
|
88
|
+
toolResult: '#a89984',
|
|
89
|
+
system: '#928374',
|
|
90
|
+
dim: '#665c54',
|
|
91
|
+
taskSummary: '#8ec07c',
|
|
92
|
+
warn: '#fabd2f',
|
|
93
|
+
error: '#fb4934',
|
|
94
|
+
success: '#b8bb26',
|
|
95
|
+
brown: '#d65d0e',
|
|
96
|
+
toggleOn: '#b8bb26',
|
|
97
|
+
},
|
|
98
|
+
// Nord — cool arctic blues, low contrast by design.
|
|
99
|
+
nord: {
|
|
100
|
+
bg: '#2e3440',
|
|
101
|
+
user: '#88c0d0',
|
|
102
|
+
userBar: '#5e81a8',
|
|
103
|
+
turnRule: '#3b4252',
|
|
104
|
+
turnLabel: '#616e88',
|
|
105
|
+
prompt: '#ebcb8b',
|
|
106
|
+
dir: '#88c0d0',
|
|
107
|
+
assistant: '#d8dee9',
|
|
108
|
+
tool: '#d08770',
|
|
109
|
+
toolResult: '#9aa7bd',
|
|
110
|
+
system: '#8b95a8',
|
|
111
|
+
dim: '#616e88',
|
|
112
|
+
taskSummary: '#81a1c1',
|
|
113
|
+
warn: '#ebcb8b',
|
|
114
|
+
error: '#bf616a',
|
|
115
|
+
success: '#a3be8c',
|
|
116
|
+
brown: '#d08770',
|
|
117
|
+
toggleOn: '#8fbcbb',
|
|
118
|
+
},
|
|
119
|
+
// Monokai — the classic Sublime Text scheme.
|
|
120
|
+
monokai: {
|
|
121
|
+
bg: '#272822',
|
|
122
|
+
user: '#66d9ef',
|
|
123
|
+
userBar: '#3f8c9e',
|
|
124
|
+
turnRule: '#3e3d32',
|
|
125
|
+
turnLabel: '#75715e',
|
|
126
|
+
prompt: '#e6db74',
|
|
127
|
+
dir: '#66d9ef',
|
|
128
|
+
assistant: '#f8f8f2',
|
|
129
|
+
tool: '#fd971f',
|
|
130
|
+
toolResult: '#a6a695',
|
|
131
|
+
system: '#a1a19a',
|
|
132
|
+
dim: '#75715e',
|
|
133
|
+
taskSummary: '#66d9ef',
|
|
134
|
+
warn: '#e6db74',
|
|
135
|
+
error: '#f92672',
|
|
136
|
+
success: '#a6e22e',
|
|
137
|
+
brown: '#fd971f',
|
|
138
|
+
toggleOn: '#a6e22e',
|
|
139
|
+
},
|
|
140
|
+
// Solarized Dark — Ethan Schoonover's precision palette.
|
|
141
|
+
'solarized-dark': {
|
|
142
|
+
bg: '#002b36',
|
|
143
|
+
user: '#268bd2',
|
|
144
|
+
userBar: '#1f6a9e',
|
|
145
|
+
turnRule: '#073642',
|
|
146
|
+
turnLabel: '#586e75',
|
|
147
|
+
prompt: '#b58900',
|
|
148
|
+
dir: '#2aa198',
|
|
149
|
+
assistant: '#93a1a1',
|
|
150
|
+
tool: '#cb4b16',
|
|
151
|
+
toolResult: '#839496',
|
|
152
|
+
system: '#839496',
|
|
153
|
+
dim: '#586e75',
|
|
154
|
+
taskSummary: '#2aa198',
|
|
155
|
+
warn: '#b58900',
|
|
156
|
+
error: '#dc322f',
|
|
157
|
+
success: '#859900',
|
|
158
|
+
brown: '#cb4b16',
|
|
159
|
+
toggleOn: '#2aa198',
|
|
160
|
+
},
|
|
161
|
+
// Tokyo Night — the modern neon-on-midnight scheme.
|
|
162
|
+
'tokyo-night': {
|
|
163
|
+
bg: '#1a1b26',
|
|
164
|
+
user: '#7dcfff',
|
|
165
|
+
userBar: '#3d7fa6',
|
|
166
|
+
turnRule: '#2a2e3f',
|
|
167
|
+
turnLabel: '#565f89',
|
|
168
|
+
prompt: '#e0af68',
|
|
169
|
+
dir: '#7dcfff',
|
|
170
|
+
assistant: '#c0caf5',
|
|
171
|
+
tool: '#ff9e64',
|
|
172
|
+
toolResult: '#9aa5ce',
|
|
173
|
+
system: '#787c99',
|
|
174
|
+
dim: '#565f89',
|
|
175
|
+
taskSummary: '#7aa2f7',
|
|
176
|
+
warn: '#e0af68',
|
|
177
|
+
error: '#f7768e',
|
|
178
|
+
success: '#9ece6a',
|
|
179
|
+
brown: '#ff9e64',
|
|
180
|
+
toggleOn: '#73daca',
|
|
181
|
+
},
|
|
49
182
|
};
|
|
183
|
+
/** The palette id used when the configured theme is unknown/missing. */
|
|
184
|
+
export const DEFAULT_THEME = 'zames';
|
|
185
|
+
/**
|
|
186
|
+
* True for a palette id we actually ship. Used by the config layer to
|
|
187
|
+
* normalize `ui.theme` (a stale value from an older/newer build must not
|
|
188
|
+
* leave the operator with an unstyled terminal).
|
|
189
|
+
*/
|
|
190
|
+
export function isThemeName(v) {
|
|
191
|
+
return (typeof v === 'string' && Object.prototype.hasOwnProperty.call(PALETTES, v));
|
|
192
|
+
}
|
|
193
|
+
/** All palette ids, in the order they are offered in /config. */
|
|
194
|
+
export function themeNames() {
|
|
195
|
+
return Object.keys(PALETTES);
|
|
196
|
+
}
|
|
197
|
+
/** Build the chalk formatters for one palette. */
|
|
198
|
+
function buildTheme(p) {
|
|
199
|
+
return {
|
|
200
|
+
user: chalk.hex(p.user),
|
|
201
|
+
userBar: chalk.hex(p.userBar),
|
|
202
|
+
turnRule: chalk.hex(p.turnRule),
|
|
203
|
+
turnLabel: chalk.hex(p.turnLabel),
|
|
204
|
+
prompt: chalk.hex(p.prompt).bold,
|
|
205
|
+
dir: chalk.hex(p.dir),
|
|
206
|
+
assistant: chalk.hex(p.assistant),
|
|
207
|
+
tool: chalk.hex(p.tool),
|
|
208
|
+
toolResult: chalk.hex(p.toolResult),
|
|
209
|
+
system: chalk.hex(p.system),
|
|
210
|
+
dim: chalk.hex(p.dim),
|
|
211
|
+
taskSummary: chalk.hex(p.taskSummary),
|
|
212
|
+
warn: chalk.hex(p.warn),
|
|
213
|
+
error: chalk.hex(p.error),
|
|
214
|
+
success: chalk.hex(p.success),
|
|
215
|
+
brown: chalk.hex(p.brown),
|
|
216
|
+
toggleOn: chalk.hex(p.toggleOn),
|
|
217
|
+
bold: chalk.bold,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// The LIVE theme object. It is mutated IN PLACE by applyTheme() so that every
|
|
221
|
+
// module that captured the import (`import { theme } from './theme.js'`) sees
|
|
222
|
+
// the switch without being re-imported — this is why the call sites read
|
|
223
|
+
// `theme.x` at RENDER time instead of destructuring a color at module load.
|
|
224
|
+
let activeName = DEFAULT_THEME;
|
|
225
|
+
export const theme = buildTheme(PALETTES[DEFAULT_THEME]);
|
|
226
|
+
/** The palette id currently rendered. */
|
|
227
|
+
export function currentThemeName() {
|
|
228
|
+
return activeName;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Switch the palette for every subsequent render. Unknown ids fall back to the
|
|
232
|
+
* default so a hand-edited config can never blank out the colors.
|
|
233
|
+
* Returns the id actually applied.
|
|
234
|
+
*/
|
|
235
|
+
export function applyTheme(name) {
|
|
236
|
+
const next = isThemeName(name) ? name : DEFAULT_THEME;
|
|
237
|
+
const built = buildTheme(PALETTES[next]);
|
|
238
|
+
for (const key of Object.keys(built)) {
|
|
239
|
+
theme[key] = built[key];
|
|
240
|
+
}
|
|
241
|
+
activeName = next;
|
|
242
|
+
return next;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The active palette's RAW hex map — for the Ink TUI, which needs color
|
|
246
|
+
* STRINGS (Ink `<Text color>` does not take chalk functions) and for the
|
|
247
|
+
* README screenshot generator, which renders the real colors as HTML.
|
|
248
|
+
*/
|
|
249
|
+
export function themeColors() {
|
|
250
|
+
return PALETTES[activeName];
|
|
251
|
+
}
|
|
50
252
|
// Horizontal rule that separates the model's answer from the next block. It
|
|
51
253
|
// spans the SHARED content width (src/width.ts) so the rule, the answers and
|
|
52
254
|
// the tool previews all line up at the same margin. The `max` argument is kept
|
package/dist/tui/AskQuestion.js
CHANGED
|
@@ -91,10 +91,10 @@ export function AskQuestion({ question, options, recommended, allowText, locale
|
|
|
91
91
|
out.push(line);
|
|
92
92
|
return out.length ? out : [''];
|
|
93
93
|
};
|
|
94
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.accent, paddingX: 2, children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + t('askuser.title') }), wrap(question).map((line, i) => (_jsx(Text, { color: UI.text, children: line }, i))), _jsx(Text, { color:
|
|
94
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.accent, paddingX: 2, children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + t('askuser.title') }), wrap(question).map((line, i) => (_jsx(Text, { color: UI.text, children: line }, i))), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), typing ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.dim, children: t('askuser.your_answer') }), _jsx(Text, { color: UI.dim, children: t('askuser.enter_send') }), _jsxs(Text, { color: UI.accent, children: ['❯ ', text, '▏'] })] })) : (rows.map((label, i) => {
|
|
95
95
|
const sel = i === cursor;
|
|
96
96
|
const isRec = i === recommended;
|
|
97
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: label }), isRec ? (_jsx(Text, { color:
|
|
97
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: label }), isRec ? (_jsx(Text, { color: UI.on, children: ' (' + t('askuser.recommended') + ')' })) : null] }, i));
|
|
98
98
|
})), _jsx(HintBar, { hints: typing
|
|
99
99
|
? [
|
|
100
100
|
['Enter', t('askuser.hint_send')],
|
package/dist/tui/ConfigMenu.js
CHANGED
|
@@ -39,6 +39,7 @@ function displayValue(field, value, t) {
|
|
|
39
39
|
}
|
|
40
40
|
export function ConfigMenuApp(opts) {
|
|
41
41
|
const { fields, t, get, set, reset, getDefault, locale = 'en' } = opts;
|
|
42
|
+
const { onAfterChange } = opts;
|
|
42
43
|
const { exit } = useApp();
|
|
43
44
|
// The hint bar is operator text: it follows the SAME locale the labels use
|
|
44
45
|
// (a hardcoded Russian bar showed up in an English UI).
|
|
@@ -89,6 +90,7 @@ export function ConfigMenuApp(opts) {
|
|
|
89
90
|
try {
|
|
90
91
|
set(f, raw);
|
|
91
92
|
bump();
|
|
93
|
+
onAfterChange?.(f);
|
|
92
94
|
setMessage(t('cfg.menu.saved', {
|
|
93
95
|
v: f.path,
|
|
94
96
|
value: displayValue(f, get(f.path), t),
|
|
@@ -105,6 +107,7 @@ export function ConfigMenuApp(opts) {
|
|
|
105
107
|
if (f.type === 'boolean') {
|
|
106
108
|
set(f, get(f.path) === true ? 'false' : 'true');
|
|
107
109
|
bump();
|
|
110
|
+
onAfterChange?.(f);
|
|
108
111
|
setMessage(null);
|
|
109
112
|
return;
|
|
110
113
|
}
|
|
@@ -125,6 +128,7 @@ export function ConfigMenuApp(opts) {
|
|
|
125
128
|
const next = f.values[(base + dir + f.values.length) % f.values.length];
|
|
126
129
|
set(f, next);
|
|
127
130
|
bump();
|
|
131
|
+
onAfterChange?.(f);
|
|
128
132
|
setMessage(null);
|
|
129
133
|
};
|
|
130
134
|
/** ←/→ acts on the focused row: cycle an enum, toggle a boolean. */
|
|
@@ -137,6 +141,7 @@ export function ConfigMenuApp(opts) {
|
|
|
137
141
|
else if (f.type === 'boolean') {
|
|
138
142
|
set(f, get(f.path) === true ? 'false' : 'true');
|
|
139
143
|
bump();
|
|
144
|
+
onAfterChange?.(f);
|
|
140
145
|
setMessage(null);
|
|
141
146
|
}
|
|
142
147
|
};
|
|
@@ -244,6 +249,7 @@ export function ConfigMenuApp(opts) {
|
|
|
244
249
|
if (pendingReset === f.path) {
|
|
245
250
|
reset(f);
|
|
246
251
|
bump();
|
|
252
|
+
onAfterChange?.(f);
|
|
247
253
|
setMessage(t('cfg.reset', { v: f.path }));
|
|
248
254
|
setPendingReset(null);
|
|
249
255
|
}
|
|
@@ -330,7 +336,7 @@ export function ConfigMenuApp(opts) {
|
|
|
330
336
|
if (!visible.length) {
|
|
331
337
|
items.push(_jsx(Text, { color: UI.dim, children: ' ' + t('cfg.menu.no_match') }, "none"));
|
|
332
338
|
}
|
|
333
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: UI.title, children: t('cfg.menu.title') }), _jsx(Text, { color: UI.dim, children: (visible.length ? cursor + 1 : 0) + '/' + visible.length })] }), _jsx(Text, { color:
|
|
339
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: UI.title, children: t('cfg.menu.title') }), _jsx(Text, { color: UI.dim, children: (visible.length ? cursor + 1 : 0) + '/' + visible.length })] }), _jsx(Text, { color: UI.border, children: '─'.repeat(Math.max(10, (columns || 80) - 6)) }), items, _jsx(Text, { color: UI.border, children: '─'.repeat(Math.max(10, (columns || 80) - 6)) }), editing ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.text, children: t('cfg.menu.edit_hint') }), _jsx(Text, { color: UI.accent, children: '❯ ' + editBuf })] })) : filterMode ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.text, children: t('cfg.menu.filter_hint') }), _jsx(Text, { color: UI.accent, children: '❯ ' + filterQuery })] })) : message ? (_jsx(Text, { color: UI.ok, children: message })) : filterQuery ? (_jsx(Text, { color: UI.dim, children: t('cfg.menu.filter_active', { q: filterQuery }) })) : null, _jsx(HintBar, { hints: [
|
|
334
340
|
['↑/↓', hint('tui.hint.select')],
|
|
335
341
|
['←/→', hint('tui.hint.change')],
|
|
336
342
|
['Enter', hint('tui.hint.edit')],
|
|
@@ -12,15 +12,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
import { useSyncExternalStore } from 'react';
|
|
13
13
|
import { Box, Text, useWindowSize, useInput } from 'ink';
|
|
14
14
|
import { LogList } from './LogView.js';
|
|
15
|
-
import { windowByRows, totalRows
|
|
15
|
+
import { windowByRows, totalRows } from './log-window.js';
|
|
16
16
|
import { StatusBar } from './StatusBar.js';
|
|
17
17
|
import { InputLine } from './InputLine.js';
|
|
18
|
-
import { contentWidth } from '../width.js';
|
|
19
18
|
import { translate } from '../i18n.js';
|
|
20
19
|
import { suggestRowCount } from '../input/suggest.js';
|
|
21
20
|
import { truncateToWidth, visLen } from '../input/layout.js';
|
|
22
21
|
import { fullScreenLayout } from './layout.js';
|
|
23
22
|
import { useMouseWheel } from './useMouseWheel.js';
|
|
23
|
+
import { UI } from './ui-colors.js';
|
|
24
24
|
/** The header bar: title, version, working directory. */
|
|
25
25
|
function Header({ store }) {
|
|
26
26
|
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
@@ -29,6 +29,9 @@ function Header({ store }) {
|
|
|
29
29
|
.filter(Boolean)
|
|
30
30
|
.join(' ');
|
|
31
31
|
const left = '❯ ' + state.title + (state.version ? ' v' + state.version : '');
|
|
32
|
+
// Referencing the nonce keeps the React dependency honest: the header colors
|
|
33
|
+
// come from the shared UI map, which is mutated in place on a theme switch.
|
|
34
|
+
void state.themeNonce;
|
|
32
35
|
const rawDir = state.workdir ? ' ' + state.workdir : '';
|
|
33
36
|
// The header MUST stay exactly ONE text row: FullScreenApp reserves
|
|
34
37
|
// HEADER_ROWS = 2 (title + divider) and the whole panel height is computed
|
|
@@ -44,7 +47,7 @@ function Header({ store }) {
|
|
|
44
47
|
dir = budget > 0 ? truncateToWidth(dir, budget) : '';
|
|
45
48
|
// If even title + provider exceed the width, drop the directory entirely.
|
|
46
49
|
const finalDir = gap >= 1 ? dir : '';
|
|
47
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color:
|
|
50
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: UI.accent, bold: true, children: left }), _jsx(Text, { color: UI.accent, children: finalDir }), _jsx(Text, { children: ' '.repeat(Math.max(1, cols - visLen(left) - visLen(finalDir) - visLen(right) - 2)) }), right ? _jsx(Text, { color: UI.on, children: right }) : null] }), _jsx(Text, { color: UI.border, children: '─'.repeat(cols) })] }));
|
|
48
51
|
}
|
|
49
52
|
export function FullScreenApp({ store, inputProps, }) {
|
|
50
53
|
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
@@ -69,22 +72,13 @@ export function FullScreenApp({ store, inputProps, }) {
|
|
|
69
72
|
// it half empty. `scroll` is measured in ROWS here (0 = newest at the
|
|
70
73
|
// bottom).
|
|
71
74
|
const all = state.log;
|
|
72
|
-
// The
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// short window, so the input rows fell below the last screen row and the
|
|
76
|
-
// caret drifted under the box while a long multi-line buffer was typed.
|
|
77
|
-
const totalFloor = totalRows(all, cols, 'floor');
|
|
75
|
+
// The scroll bound uses the EXACT row count: it is what the content really
|
|
76
|
+
// renders, so the newest row stays reachable. (Reserving the viewport with
|
|
77
|
+
// the floor is a separate concern, handled in `fullScreenLayout`.)
|
|
78
78
|
const total = totalRows(all, cols);
|
|
79
79
|
const maxScroll = Math.max(0, total - logHeight);
|
|
80
80
|
const scroll = Math.max(0, Math.min(state.scroll, maxScroll));
|
|
81
81
|
const visible = windowByRows(all, logHeight, scroll, cols);
|
|
82
|
-
// Rows the WINDOWED content really needs. The reservation above rounds DOWN
|
|
83
|
-
// per entry (floor), so the difference is the slack the content needs. The
|
|
84
|
-
// box takes at most `logHeight` and at least one row; never more, because
|
|
85
|
-
// that is exactly what pushed the input rows off a short screen.
|
|
86
|
-
const filledRows = visible.reduce((n, e) => n + entryRows(e, cols), 0);
|
|
87
|
-
const logBoxHeight = Math.max(1, Math.min(logHeight, Math.max(filledRows, 1)));
|
|
88
82
|
// Scroll the log with PgUp/PgDn; Home/End jump to the very top/bottom. The
|
|
89
83
|
// input line OWNS Home/End while its buffer is non-empty (they move the
|
|
90
84
|
// caret), so the jump only applies to an EMPTY buffer — which is exactly
|
|
@@ -117,13 +111,12 @@ export function FullScreenApp({ store, inputProps, }) {
|
|
|
117
111
|
const next = cur + delta * 3;
|
|
118
112
|
store.set({ scroll: Math.max(0, Math.min(maxScroll, next)) });
|
|
119
113
|
}, state.mouseScroll);
|
|
120
|
-
return (_jsxs(Box, { flexDirection: "column", height: termRows, children: [_jsx(Header, { store: store }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 1, overflow: "hidden", justifyContent: "flex-end", children: all.length === 0 ? (_jsx(Text, { color:
|
|
114
|
+
return (_jsxs(Box, { flexDirection: "column", height: termRows, children: [_jsx(Header, { store: store }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 1, overflow: "hidden", justifyContent: "flex-end", children: all.length === 0 ? (_jsx(Text, { color: UI.dim, children: translate(state.locale)('editor.empty_hint') })) : (_jsx(LogList, { entries: visible })) }), _jsx(StatusBar, { state: state, divider: false }), _jsx(InputLine, { ...inputProps, store: store,
|
|
121
115
|
// Cursor rows above the input box: header + log viewport + status.
|
|
122
116
|
// The log pane is FLEXIBLE now, so the panel below it starts right
|
|
123
117
|
// after everything above: header + as many log rows as fit + the
|
|
124
118
|
// suggestion popup + status. `cursorTop` counts the rows ABOVE the
|
|
125
119
|
// input rows; the box border row is added by InputLine.
|
|
126
|
-
cursorTop: fsLayout.cursorTop, prompt: state.prompt, locale: state.locale, commands: state.commands, locked: state.locked }), _jsx(Text, { color:
|
|
120
|
+
cursorTop: fsLayout.cursorTop, prompt: state.prompt, locale: state.locale, commands: state.commands, locked: state.locked }), _jsx(Text, { color: UI.dim, wrap: "truncate", children: ' ' +
|
|
127
121
|
translate(state.locale)(state.mouseScroll ? 'editor.keys_hint' : 'editor.keys_hint_select') })] }));
|
|
128
122
|
}
|
|
129
|
-
void contentWidth;
|
package/dist/tui/InputLine.js
CHANGED
|
@@ -16,10 +16,11 @@ import { insertText, insertPaste, insertNewline, backspace, del, moveLeft, moveR
|
|
|
16
16
|
import { dispatchKey, parseCsiU } from './keys.js';
|
|
17
17
|
import { useSyncExternalStore } from 'react';
|
|
18
18
|
import { matchSlashCommands, suggestWindow, completeSlashCommand, } from '../input/suggest.js';
|
|
19
|
+
import { UI } from './ui-colors.js';
|
|
19
20
|
/** Render the visual rows of the buffer with the prompt on the first row. */
|
|
20
21
|
function InputRows({ prompt, buf, cols, }) {
|
|
21
22
|
const lay = layoutInput(prompt, buf, 0, cols);
|
|
22
|
-
return (_jsx(Box, { flexDirection: "column", children: lay.rows.map((r, i) => (_jsxs(Text, { children: [_jsx(Text, { color:
|
|
23
|
+
return (_jsx(Box, { flexDirection: "column", children: lay.rows.map((r, i) => (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, bold: true, children: r.prefix }), r.text] }, i))) }));
|
|
23
24
|
}
|
|
24
25
|
export function InputLine(props) {
|
|
25
26
|
const { columns } = useWindowSize();
|
|
@@ -316,9 +317,9 @@ export function InputLine(props) {
|
|
|
316
317
|
return (_jsxs(Box, { flexDirection: "column", children: [suggestions.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [shown.map((c, i) => {
|
|
317
318
|
const label = c.hint ? c.name + ' ' + c.hint : c.name;
|
|
318
319
|
const selected = suggestOffset + i === suggestSelected;
|
|
319
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color:
|
|
320
|
-
}), moreCount > 0 && (_jsx(Text, { color:
|
|
320
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: selected ? ' ▸ ' : ' ' }), _jsx(Text, { color: UI.accent, children: label }), _jsx(Text, { color: UI.dim, children: ' ' + c.description })] }, c.name));
|
|
321
|
+
}), moreCount > 0 && (_jsx(Text, { color: UI.dim, children: ' ' +
|
|
321
322
|
t('editor.more', { n: String(moreCount) }) +
|
|
322
323
|
' ' +
|
|
323
|
-
t('editor.page_hint') }))] })), _jsx(Box, { borderStyle: "round", borderColor: props.locked ?
|
|
324
|
+
t('editor.page_hint') }))] })), _jsx(Box, { borderStyle: "round", borderColor: props.locked ? UI.dim : UI.accent, paddingLeft: 1, paddingRight: 1, flexDirection: "column", children: _jsx(InputRows, { prompt: props.prompt, buf: st.buf, cols: innerCols }) })] }));
|
|
324
325
|
}
|
package/dist/tui/LogView.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Static, Box, Text } from 'ink';
|
|
|
3
3
|
import { theme } from '../theme.js';
|
|
4
4
|
/** One log entry. `raw` items already carry ANSI colors and are printed
|
|
5
5
|
* verbatim; structured items get a themed prefix. */
|
|
6
|
-
|
|
6
|
+
function LogRow({ entry }) {
|
|
7
7
|
if (entry.raw) {
|
|
8
8
|
// Pre-rendered (markdown answer, tool preview). Print as-is; Ink passes
|
|
9
9
|
// embedded ANSI escape sequences through to the terminal.
|
package/dist/tui/SelectList.js
CHANGED
|
@@ -44,7 +44,7 @@ export function SelectList({ title, items, initial, locale = 'en', onSelect, })
|
|
|
44
44
|
finish(items[cursor]?.value ?? null);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + title }), _jsx(Text, { color: UI.dim, children: items.length })] }), _jsx(Text, { color:
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + title }), _jsx(Text, { color: UI.dim, children: items.length })] }), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), items.slice(win.top, win.end).map((it, i) => {
|
|
48
48
|
const idx = win.top + i;
|
|
49
49
|
const sel = idx === cursor;
|
|
50
50
|
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: it.label }), it.detail ? _jsx(Text, { color: UI.dim, children: ' ' + it.detail }) : null] }, it.value));
|
package/dist/tui/StatusBar.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text, useAnimation, useWindowSize } from 'ink';
|
|
3
3
|
import { translate } from '../i18n.js';
|
|
4
4
|
import { DOTS, stripEllipsis } from '../spinner.js';
|
|
5
|
+
import { UI } from './ui-colors.js';
|
|
5
6
|
import { truncateToWidth, visLen } from '../input/layout.js';
|
|
6
7
|
/** Elapsed seconds of the current animated phase ("0s", "45s", "2m 05s"). */
|
|
7
8
|
function elapsedLabel(startedAt, animating) {
|
|
@@ -68,16 +69,16 @@ function Badges({ state, budget = Infinity, }) {
|
|
|
68
69
|
all.push({ key, w, node });
|
|
69
70
|
};
|
|
70
71
|
if (state.statusLine) {
|
|
71
|
-
add('sl', visLen(state.statusLine) + 1, _jsxs(Text, { color:
|
|
72
|
+
add('sl', visLen(state.statusLine) + 1, _jsxs(Text, { color: UI.system, children: [state.statusLine, ' '] }, "sl"));
|
|
72
73
|
}
|
|
73
74
|
if (state.provider) {
|
|
74
|
-
add('prov', visLen('[' + state.provider + '] '), _jsx(Text, { color:
|
|
75
|
+
add('prov', visLen('[' + state.provider + '] '), _jsx(Text, { color: UI.system, children: '[' + state.provider + '] ' }, "prov"));
|
|
75
76
|
}
|
|
76
77
|
if (state.queueLength > 0) {
|
|
77
|
-
add('q', visLen('⧗' + state.queueLength + ' '), _jsxs(Text, { color:
|
|
78
|
+
add('q', visLen('⧗' + state.queueLength + ' '), _jsxs(Text, { color: UI.warn, children: ['⧗' + state.queueLength, ' '] }, "q"));
|
|
78
79
|
}
|
|
79
80
|
if (state.tasks) {
|
|
80
|
-
add('t', visLen(state.tasks) + 1, _jsxs(Text, { color:
|
|
81
|
+
add('t', visLen(state.tasks) + 1, _jsxs(Text, { color: UI.system, children: [state.tasks, ' '] }, "t"));
|
|
81
82
|
}
|
|
82
83
|
const toggles = [];
|
|
83
84
|
if (state.thinkingEnabled)
|
|
@@ -86,14 +87,14 @@ function Badges({ state, budget = Infinity, }) {
|
|
|
86
87
|
toggles.push('🌐\uFE0E');
|
|
87
88
|
if (toggles.length) {
|
|
88
89
|
const label = toggles.join(' ') + ' ';
|
|
89
|
-
add('tg', visLen(label), _jsx(Text, { color:
|
|
90
|
+
add('tg', visLen(label), _jsx(Text, { color: UI.on, children: label }, "tg"));
|
|
90
91
|
}
|
|
91
92
|
if (state.contextText) {
|
|
92
93
|
const color = state.contextLevel === 'high'
|
|
93
|
-
?
|
|
94
|
+
? UI.err
|
|
94
95
|
: state.contextLevel === 'warn'
|
|
95
|
-
?
|
|
96
|
-
:
|
|
96
|
+
? UI.warn
|
|
97
|
+
: UI.ok;
|
|
97
98
|
const marker = state.contextLevel === 'high'
|
|
98
99
|
? '⛔ '
|
|
99
100
|
: state.contextLevel === 'warn'
|
|
@@ -161,5 +162,5 @@ export function StatusBar({ state, divider = true, }) {
|
|
|
161
162
|
const cols = useWindowSize().columns || 80;
|
|
162
163
|
const leftBudget = Math.max(0, Math.min(Math.floor(cols / 3), cols - 14));
|
|
163
164
|
const leftOut = showLeft ? truncateToWidth(leftText, leftBudget) : '';
|
|
164
|
-
return (_jsxs(Box, { flexDirection: "column", children: [divider ? _jsx(Text, { color:
|
|
165
|
+
return (_jsxs(Box, { flexDirection: "column", children: [divider ? _jsx(Text, { color: UI.border, children: '─'.repeat(cols) }) : null, _jsxs(Box, { flexDirection: "row", width: Math.max(1, cols - 2), height: 1, children: [_jsx(Text, { color: UI.accentDim, children: leftOut || ' ' }), _jsx(Box, { flexGrow: 1 }), _jsx(Badges, { state: state, budget: Math.max(0, cols - 2 - visLen(leftOut)) })] })] }));
|
|
165
166
|
}
|
package/dist/tui/TuiEditor.js
CHANGED
|
@@ -344,6 +344,15 @@ export class TuiEditor {
|
|
|
344
344
|
this.locale = locale;
|
|
345
345
|
this.store.set({ locale });
|
|
346
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Repaint the Ink widgets after a palette switch. The hex map (UI) is
|
|
349
|
+
* mutated in place by theme.applyTheme(), but Ink only re-renders when the
|
|
350
|
+
* store emits — bump a nonce so every subscribed component redraws with the
|
|
351
|
+
* new colors.
|
|
352
|
+
*/
|
|
353
|
+
setTheme(_colors) {
|
|
354
|
+
this.store.set({ themeNonce: Date.now() });
|
|
355
|
+
}
|
|
347
356
|
setCommands(commands) {
|
|
348
357
|
this.commands = commands;
|
|
349
358
|
this.store.set({ commands });
|
package/dist/tui/edit.js
CHANGED
|
@@ -292,7 +292,7 @@ export function searchStart(s) {
|
|
|
292
292
|
};
|
|
293
293
|
return searchRefresh(st, true);
|
|
294
294
|
}
|
|
295
|
-
|
|
295
|
+
function searchRefresh(s, resetToNewest) {
|
|
296
296
|
const q = s.searchQuery;
|
|
297
297
|
const index = resetToNewest ? s.history.length - 1 : s.searchIndex;
|
|
298
298
|
for (let i = index; i >= 0; i--) {
|
package/dist/tui/layout.js
CHANGED
|
@@ -7,7 +7,7 @@ import { inputBoxRows } from '../input/layout.js';
|
|
|
7
7
|
/** Rows of the fixed header: the title row + the divider. */
|
|
8
8
|
export const HEADER_ROWS = 2;
|
|
9
9
|
/** Rows of the status bar above the input box. */
|
|
10
|
-
|
|
10
|
+
const STATUS_ROWS = 1;
|
|
11
11
|
/** Rows of the key hint UNDER the input box. */
|
|
12
12
|
export const HINT_ROWS = 1;
|
|
13
13
|
export function fullScreenLayout(input) {
|
package/dist/tui/store.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The Ink TUI's color map, derived from the ACTIVE terminal palette
|
|
2
|
+
// (src/theme.ts). Ink `<Text color>` takes a plain hex STRING, not a chalk
|
|
3
|
+
// function, so the TUI cannot reuse `theme` directly — this module is the one
|
|
4
|
+
// bridge between the two.
|
|
5
|
+
//
|
|
6
|
+
// `UI` is mutated IN PLACE by syncUiColors() (like `theme`): every component
|
|
7
|
+
// reads `UI.x` at RENDER time, so a /config theme switch repaints without
|
|
8
|
+
// re-importing anything. Components must never destructure a color at module
|
|
9
|
+
// load — that would freeze the palette at startup.
|
|
10
|
+
import { PALETTES, DEFAULT_THEME, isThemeName } from '../theme.js';
|
|
11
|
+
/** Derive the Ink map from a terminal palette. */
|
|
12
|
+
export function uiColorsFrom(p) {
|
|
13
|
+
return {
|
|
14
|
+
accent: p.prompt,
|
|
15
|
+
accentDim: p.turnLabel,
|
|
16
|
+
text: p.assistant,
|
|
17
|
+
dim: p.dim,
|
|
18
|
+
border: p.turnRule,
|
|
19
|
+
on: p.toggleOn,
|
|
20
|
+
off: p.dim,
|
|
21
|
+
ok: p.success,
|
|
22
|
+
warn: p.warn,
|
|
23
|
+
err: p.error,
|
|
24
|
+
system: p.system,
|
|
25
|
+
title: p.taskSummary,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export const UI = uiColorsFrom(PALETTES[DEFAULT_THEME]);
|
|
29
|
+
/** Repaint the Ink widgets with the palette of `name` (unknown -> default). */
|
|
30
|
+
export function syncUiColors(name) {
|
|
31
|
+
const id = isThemeName(name) ? name : DEFAULT_THEME;
|
|
32
|
+
const next = uiColorsFrom(PALETTES[id]);
|
|
33
|
+
for (const key of Object.keys(next)) {
|
|
34
|
+
UI[key] = next[key];
|
|
35
|
+
}
|
|
36
|
+
return id;
|
|
37
|
+
}
|