zames_pro 2.83.2 → 2.84.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 +20 -2
- package/dist/config.js +8 -0
- package/dist/i18n/config.js +4 -0
- package/dist/i18n/deepseek.js +8 -0
- package/dist/index.js +20 -1
- package/dist/input/layout.js +10 -8
- package/dist/theme.js +128 -0
- package/dist/tui/ConfigMenu.js +33 -1
- package/dist/tui/FullScreenApp.js +2 -1
- package/dist/tui/InputLine.js +8 -4
- package/dist/tui/StatusBar.js +27 -15
- package/dist/tui/ThemePicker.js +2 -16
- package/dist/tui/layout.js +1 -1
- package/dist/tui/theme-preview.js +45 -0
- package/dist/tui/ui-colors.js +30 -0
- package/llms-full.txt +10 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.84.0] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **ui:** theme previews, 6 more palettes, inline /config preview
|
|
15
|
+
|
|
16
|
+
## [2.83.4] - 2026-10-27
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **ui:** theme the input text, the status row and the input frame
|
|
21
|
+
|
|
10
22
|
## [2.83.2] - 2026-10-27
|
|
11
23
|
|
|
12
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -631,7 +631,16 @@ The switch is instant, no restart. `zames` is the default; the bundled palettes
|
|
|
631
631
|
are ports of well-known editor schemes:
|
|
632
632
|
|
|
633
633
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
634
|
-
`solarized-dark`, `tokyo-night
|
|
634
|
+
`solarized-dark`, `tokyo-night`, `catppuccin`, `github-dark`, `ayu`,
|
|
635
|
+
`rose-pine`, `everforest`, `kanagawa`.
|
|
636
|
+
|
|
637
|
+
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
638
|
+
highlighting inside them, the status row and the input frame. To make the choice
|
|
639
|
+
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
640
|
+
below the list, in `/config` press `p` on the theme row); after
|
|
641
|
+
`/theme <id>` the sample is printed too. The frame itself is a separate
|
|
642
|
+
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
643
|
+
(default `round`; `none` removes it).
|
|
635
644
|
|
|
636
645
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
637
646
|
|
|
@@ -1354,7 +1363,16 @@ enum переключаются на месте; числа и строки от
|
|
|
1354
1363
|
редакторов:
|
|
1355
1364
|
|
|
1356
1365
|
`zames` (по умолчанию), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
1357
|
-
`solarized-dark`, `tokyo-night
|
|
1366
|
+
`solarized-dark`, `tokyo-night`, `catppuccin`, `github-dark`, `ayu`,
|
|
1367
|
+
`rose-pine`, `everforest`, `kanagawa`.
|
|
1368
|
+
|
|
1369
|
+
Тема перекрашивает ВСЁ: интерфейс, ответы агента, подсветку кода в ответах,
|
|
1370
|
+
строку статуса и рамку ввода. Чтобы было видно, ЧТО меняется, перед выбором
|
|
1371
|
+
показывается образец сессии в этой палитре (в `/theme` — под списком, в
|
|
1372
|
+
`/config` — клавиша `p` на строке темы). После применения `/theme <id>`
|
|
1373
|
+
образец печатается в терминал. Саму рамку можно менять отдельно:
|
|
1374
|
+
`/config set ui.inputBorder <round|single|double|bold|none>` (по умолчанию
|
|
1375
|
+
`round`, `none` — без рамки).
|
|
1358
1376
|
|
|
1359
1377
|
Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo,
|
|
1360
1378
|
сессии.
|
package/dist/config.js
CHANGED
|
@@ -134,6 +134,7 @@ export const DEFAULTS = {
|
|
|
134
134
|
ui: {
|
|
135
135
|
locale: DEFAULT_LOCALE,
|
|
136
136
|
theme: DEFAULT_THEME,
|
|
137
|
+
inputBorder: 'round',
|
|
137
138
|
contextLimit: 1_000_000,
|
|
138
139
|
// 0 = auto: terminal width, capped at 100 columns (the historical width).
|
|
139
140
|
answerWidth: 0,
|
|
@@ -291,6 +292,13 @@ export const CONFIG_SCHEMA = [
|
|
|
291
292
|
labelKey: 'cfg.f.maxIterations',
|
|
292
293
|
groupKey: 'cfg.group.agent',
|
|
293
294
|
},
|
|
295
|
+
{
|
|
296
|
+
path: 'ui.inputBorder',
|
|
297
|
+
type: 'enum',
|
|
298
|
+
values: ['round', 'single', 'double', 'bold', 'none'],
|
|
299
|
+
labelKey: 'cfg.f.ui_inputBorder',
|
|
300
|
+
groupKey: 'cfg.group.ui',
|
|
301
|
+
},
|
|
294
302
|
{
|
|
295
303
|
path: 'ui.theme',
|
|
296
304
|
type: 'enum',
|
package/dist/i18n/config.js
CHANGED
|
@@ -55,6 +55,10 @@ export const configMessages = {
|
|
|
55
55
|
ru: 'Язык интерфейса и ответов агента',
|
|
56
56
|
en: 'Interface and agent reply language',
|
|
57
57
|
},
|
|
58
|
+
'cfg.f.ui_inputBorder': {
|
|
59
|
+
ru: 'Рамка строки ввода: round | single | double | bold | none',
|
|
60
|
+
en: 'Input box frame: round | single | double | bold | none',
|
|
61
|
+
},
|
|
58
62
|
'cfg.f.ui_theme': {
|
|
59
63
|
ru: 'Цветовая тема терминала',
|
|
60
64
|
en: 'Terminal color theme',
|
package/dist/i18n/deepseek.js
CHANGED
|
@@ -212,6 +212,14 @@ export const deepseekMessages = {
|
|
|
212
212
|
ru: '✔ Сохранено: {v} = {value}',
|
|
213
213
|
en: '✔ Saved: {v} = {value}',
|
|
214
214
|
},
|
|
215
|
+
'cfg.menu.theme_preview': {
|
|
216
|
+
ru: 'Так тема выглядит в работе:',
|
|
217
|
+
en: 'How the theme looks in a session:',
|
|
218
|
+
},
|
|
219
|
+
'cfg.menu.theme_preview_key': {
|
|
220
|
+
ru: 'пример темы',
|
|
221
|
+
en: 'theme sample',
|
|
222
|
+
},
|
|
215
223
|
'cfg.menu.default': { ru: 'по умолчанию', en: 'default' },
|
|
216
224
|
'cfg.menu.filter_hint': {
|
|
217
225
|
ru: '/ — поиск по настройкам, d — сбросить (дважды), * — изменено от дефолта',
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import fs from 'fs/promises';
|
|
|
5
5
|
import { readFileSync, statSync } from 'fs';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { theme, applyTheme, themeColors, themeNames, currentThemeName, isThemeName, } from './theme.js';
|
|
8
|
+
import { themePreviewAnsi } from './tui/theme-preview.js';
|
|
9
|
+
import { setInputBorder } from './tui/ui-colors.js';
|
|
8
10
|
import { DeepSeekBrowser, headlessUACacheReady } from './browser.js';
|
|
9
11
|
import { QwenBrowser } from './qwen-browser.js';
|
|
10
12
|
import { GlmBrowser } from './glm-browser.js';
|
|
@@ -123,6 +125,10 @@ setAnswerWidth(config.ui?.answerWidth ?? 0);
|
|
|
123
125
|
// banner, startup notices): theme.ts starts on the default and every call site
|
|
124
126
|
// reads the live object, so one apply here repaints the whole session.
|
|
125
127
|
applyTheme(config.ui?.theme);
|
|
128
|
+
// Same for the input-box FRAME style: the TUI reads it per render, so one
|
|
129
|
+
// apply before the editor is created covers the first paint (a /config change
|
|
130
|
+
// goes through setConfigRuntime below).
|
|
131
|
+
setInputBorder(config.ui?.inputBorder);
|
|
126
132
|
// Current interface/agent language. Changed by the /config lang <ru|en> command.
|
|
127
133
|
let currentLocale = isLocale(config.ui?.locale)
|
|
128
134
|
? config.ui.locale
|
|
@@ -1943,6 +1949,11 @@ async function main() {
|
|
|
1943
1949
|
// object is shared and mutated in place, so every later render uses it;
|
|
1944
1950
|
// the editor is asked for a full redraw because the rows already on screen
|
|
1945
1951
|
// (status, prompt, hints) were painted with the OLD colors.
|
|
1952
|
+
if (path === 'ui.inputBorder') {
|
|
1953
|
+
setInputBorder(value);
|
|
1954
|
+
if (editor)
|
|
1955
|
+
editor.setTheme?.(themeColors());
|
|
1956
|
+
}
|
|
1946
1957
|
if (path === 'ui.theme') {
|
|
1947
1958
|
const applied = applyTheme(value);
|
|
1948
1959
|
config.ui.theme = applied;
|
|
@@ -2176,8 +2187,9 @@ async function main() {
|
|
|
2176
2187
|
// read the shared UI map, and without a bump the operator would cycle
|
|
2177
2188
|
// palette ids while the colors stayed those of the previous one.
|
|
2178
2189
|
onAfterChange: (field) => {
|
|
2179
|
-
if (field.path === 'ui.theme')
|
|
2190
|
+
if (field.path === 'ui.theme' || field.path === 'ui.inputBorder') {
|
|
2180
2191
|
editor?.setTheme?.(themeColors());
|
|
2192
|
+
}
|
|
2181
2193
|
},
|
|
2182
2194
|
locale: currentLocale,
|
|
2183
2195
|
};
|
|
@@ -2238,6 +2250,11 @@ async function main() {
|
|
|
2238
2250
|
return;
|
|
2239
2251
|
}
|
|
2240
2252
|
emit(theme.assistant(t('theme.set', { v: applyThemeById(arg) })));
|
|
2253
|
+
// A terminal can only show colors it can paint, and the operator asked
|
|
2254
|
+
// "what exactly changes?" — print a sample of every role in the new
|
|
2255
|
+
// palette right below the confirmation.
|
|
2256
|
+
emit('');
|
|
2257
|
+
emit(themePreviewAnsi(arg));
|
|
2241
2258
|
return;
|
|
2242
2259
|
}
|
|
2243
2260
|
// No argument: a pick list in the TUI, the id list otherwise. The cursor
|
|
@@ -2267,6 +2284,8 @@ async function main() {
|
|
|
2267
2284
|
}
|
|
2268
2285
|
if (picked) {
|
|
2269
2286
|
emit(theme.assistant(t('theme.set', { v: applyThemeById(picked) })));
|
|
2287
|
+
emit('');
|
|
2288
|
+
emit(themePreviewAnsi(picked));
|
|
2270
2289
|
}
|
|
2271
2290
|
else {
|
|
2272
2291
|
// Cancelled: the previews must not stick. Restore WITHOUT persisting.
|
package/dist/input/layout.js
CHANGED
|
@@ -353,24 +353,26 @@ export function truncateToWidth(s, maxCols) {
|
|
|
353
353
|
const reset = out.includes(ESC + '[') ? ESC + '[0m' : '';
|
|
354
354
|
return out + reset + '…';
|
|
355
355
|
}
|
|
356
|
-
// Inner content width of the framed input box: the
|
|
357
|
-
//
|
|
358
|
-
// the text
|
|
356
|
+
// Inner content width of the framed input box: the border takes 1 column on
|
|
357
|
+
// each side (2 total, 0 when ui.inputBorder is 'none') and the box adds
|
|
358
|
+
// 1 column of padding on each side, so the text wraps to `cols - border - 2`. Exported so the layout ABOVE the box
|
|
359
359
|
// (FullScreenApp) and the box itself (InputLine) use ONE number — a mismatch
|
|
360
360
|
// made the reserved height disagree with the rendered rows and the panel
|
|
361
361
|
// overflowed.
|
|
362
|
-
export function inputInnerCols(cols) {
|
|
363
|
-
return Math.max(4, (Number(cols) || 80) -
|
|
362
|
+
export function inputInnerCols(cols, borderCols = 2) {
|
|
363
|
+
return Math.max(4, (Number(cols) || 80) - borderCols - 2);
|
|
364
364
|
}
|
|
365
365
|
// Real height (rows) of the framed input box for the CURRENT buffer: 2 border
|
|
366
366
|
// rows + the number of VISUAL rows the buffer wraps to. Using the LOGICAL line
|
|
367
367
|
// count (buf.split('\n').length) was wrong: a long line wraps and renders MORE
|
|
368
368
|
// rows than it has newlines, so the box was reserved too few rows, the second
|
|
369
369
|
// line printed OVER the first and the cursor landed on the wrong row.
|
|
370
|
-
export function inputBoxRows(prompt, buf, cursor, cols) {
|
|
371
|
-
const inner = inputInnerCols(cols);
|
|
370
|
+
export function inputBoxRows(prompt, buf, cursor, cols, borderCols = 2) {
|
|
371
|
+
const inner = inputInnerCols(cols, borderCols);
|
|
372
372
|
const rows = layoutInput(prompt, buf, cursor, inner).rows.length;
|
|
373
|
-
|
|
373
|
+
// Two BORDER rows only when a frame is drawn ('none' reserves just the text).
|
|
374
|
+
const borderRows = borderCols > 0 ? 2 : 0;
|
|
375
|
+
return borderRows + Math.max(1, rows);
|
|
374
376
|
}
|
|
375
377
|
export function layoutInput(promptStr, buf, cursor, cols) {
|
|
376
378
|
// Reserve the LAST column. If a printed row reaches exactly `cols` columns,
|
package/dist/theme.js
CHANGED
|
@@ -179,6 +179,134 @@ export const PALETTES = {
|
|
|
179
179
|
brown: '#ff9e64',
|
|
180
180
|
toggleOn: '#73daca',
|
|
181
181
|
},
|
|
182
|
+
// Catppuccin Mocha — the soft pastel palette that took over the editor
|
|
183
|
+
// ecosystem (Neovim, VS Code, terminals).
|
|
184
|
+
catppuccin: {
|
|
185
|
+
bg: '#1e1e2e',
|
|
186
|
+
user: '#89b4fa',
|
|
187
|
+
userBar: '#5477a8',
|
|
188
|
+
turnRule: '#313244',
|
|
189
|
+
turnLabel: '#6c7086',
|
|
190
|
+
prompt: '#f9e2af',
|
|
191
|
+
dir: '#89dceb',
|
|
192
|
+
assistant: '#cdd6f4',
|
|
193
|
+
tool: '#fab387',
|
|
194
|
+
toolResult: '#a6adc8',
|
|
195
|
+
system: '#9399b2',
|
|
196
|
+
dim: '#6c7086',
|
|
197
|
+
taskSummary: '#89b4fa',
|
|
198
|
+
warn: '#f9e2af',
|
|
199
|
+
error: '#f38ba8',
|
|
200
|
+
success: '#a6e3a1',
|
|
201
|
+
brown: '#eba0ac',
|
|
202
|
+
toggleOn: '#94e2d5',
|
|
203
|
+
},
|
|
204
|
+
// GitHub Dark — the palette of GitHub's dark UI (familiar to anyone who
|
|
205
|
+
// reads diffs all day).
|
|
206
|
+
'github-dark': {
|
|
207
|
+
bg: '#0d1117',
|
|
208
|
+
user: '#58a6ff',
|
|
209
|
+
userBar: '#3a6ea8',
|
|
210
|
+
turnRule: '#21262d',
|
|
211
|
+
turnLabel: '#6e7681',
|
|
212
|
+
prompt: '#d29922',
|
|
213
|
+
dir: '#79c0ff',
|
|
214
|
+
assistant: '#c9d1d9',
|
|
215
|
+
tool: '#ffa657',
|
|
216
|
+
toolResult: '#8b949e',
|
|
217
|
+
system: '#8b949e',
|
|
218
|
+
dim: '#6e7681',
|
|
219
|
+
taskSummary: '#a5d6ff',
|
|
220
|
+
warn: '#d29922',
|
|
221
|
+
error: '#f85149',
|
|
222
|
+
success: '#3fb950',
|
|
223
|
+
brown: '#db6d28',
|
|
224
|
+
toggleOn: '#56d4dd',
|
|
225
|
+
},
|
|
226
|
+
// Ayu Dark — the minimal warm-accent scheme from the Ayu editor theme.
|
|
227
|
+
ayu: {
|
|
228
|
+
bg: '#0f1419',
|
|
229
|
+
user: '#59c2ff',
|
|
230
|
+
userBar: '#35789e',
|
|
231
|
+
turnRule: '#1f2430',
|
|
232
|
+
turnLabel: '#5c6773',
|
|
233
|
+
prompt: '#ffb454',
|
|
234
|
+
dir: '#59c2ff',
|
|
235
|
+
assistant: '#c7ccd1',
|
|
236
|
+
tool: '#ff8f40',
|
|
237
|
+
toolResult: '#a0a8b0',
|
|
238
|
+
system: '#8a9199',
|
|
239
|
+
dim: '#5c6773',
|
|
240
|
+
taskSummary: '#39bae6',
|
|
241
|
+
warn: '#ffb454',
|
|
242
|
+
error: '#f07178',
|
|
243
|
+
success: '#aad94c',
|
|
244
|
+
brown: '#e6b450',
|
|
245
|
+
toggleOn: '#95e6cb',
|
|
246
|
+
},
|
|
247
|
+
// Rosé Pine — muted rose-pine tones, a calmer alternative to Dracula.
|
|
248
|
+
'rose-pine': {
|
|
249
|
+
bg: '#191724',
|
|
250
|
+
user: '#9ccfd8',
|
|
251
|
+
userBar: '#5b8490',
|
|
252
|
+
turnRule: '#26233a',
|
|
253
|
+
turnLabel: '#6e6a86',
|
|
254
|
+
prompt: '#f6c177',
|
|
255
|
+
dir: '#9ccfd8',
|
|
256
|
+
assistant: '#e0def4',
|
|
257
|
+
tool: '#ebbcba',
|
|
258
|
+
toolResult: '#908caa',
|
|
259
|
+
system: '#908caa',
|
|
260
|
+
dim: '#6e6a86',
|
|
261
|
+
taskSummary: '#c4a7e7',
|
|
262
|
+
warn: '#f6c177',
|
|
263
|
+
error: '#eb6f92',
|
|
264
|
+
success: '#31748f',
|
|
265
|
+
brown: '#ebbcba',
|
|
266
|
+
toggleOn: '#9ccfd8',
|
|
267
|
+
},
|
|
268
|
+
// Everforest — the soft green forest palette (low contrast, easy on eyes).
|
|
269
|
+
everforest: {
|
|
270
|
+
bg: '#2d353b',
|
|
271
|
+
user: '#7fbbb3',
|
|
272
|
+
userBar: '#5a8b85',
|
|
273
|
+
turnRule: '#3d484d',
|
|
274
|
+
turnLabel: '#859289',
|
|
275
|
+
prompt: '#dbbc7f',
|
|
276
|
+
dir: '#7fbbb3',
|
|
277
|
+
assistant: '#d3c6aa',
|
|
278
|
+
tool: '#e69875',
|
|
279
|
+
toolResult: '#9da9a0',
|
|
280
|
+
system: '#859289',
|
|
281
|
+
dim: '#7a8478',
|
|
282
|
+
taskSummary: '#a7c080',
|
|
283
|
+
warn: '#dbbc7f',
|
|
284
|
+
error: '#e67e80',
|
|
285
|
+
success: '#a7c080',
|
|
286
|
+
brown: '#e69875',
|
|
287
|
+
toggleOn: '#83c092',
|
|
288
|
+
},
|
|
289
|
+
// Kanagawa — Hokusai-inspired muted indigo (the "wave" theme).
|
|
290
|
+
kanagawa: {
|
|
291
|
+
bg: '#1f1f28',
|
|
292
|
+
user: '#7e9cd8',
|
|
293
|
+
userBar: '#55709e',
|
|
294
|
+
turnRule: '#2a2a37',
|
|
295
|
+
turnLabel: '#727169',
|
|
296
|
+
prompt: '#e6c384',
|
|
297
|
+
dir: '#7fb4ca',
|
|
298
|
+
assistant: '#dcd7ba',
|
|
299
|
+
tool: '#ffa066',
|
|
300
|
+
toolResult: '#a6a69c',
|
|
301
|
+
system: '#c8c093',
|
|
302
|
+
dim: '#727169',
|
|
303
|
+
taskSummary: '#957fb8',
|
|
304
|
+
warn: '#e6c384',
|
|
305
|
+
error: '#e46876',
|
|
306
|
+
success: '#98bb6c',
|
|
307
|
+
brown: '#d27e59',
|
|
308
|
+
toggleOn: '#7aa89f',
|
|
309
|
+
},
|
|
182
310
|
};
|
|
183
311
|
/** The palette id used when the configured theme is unknown/missing. */
|
|
184
312
|
export const DEFAULT_THEME = 'zames';
|
package/dist/tui/ConfigMenu.js
CHANGED
|
@@ -14,6 +14,7 @@ import { useMemo, useState } from 'react';
|
|
|
14
14
|
import { Box, Text, useInput, useApp, useWindowSize } from 'ink';
|
|
15
15
|
import { translate } from '../i18n.js';
|
|
16
16
|
import { Checkbox, Select, UI, HintBar } from './widgets.js';
|
|
17
|
+
import { ThemePreview } from './theme-preview.js';
|
|
17
18
|
/** Strip ANSI to measure a line's visible width. */
|
|
18
19
|
function stripAnsi(s) {
|
|
19
20
|
return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
@@ -52,6 +53,8 @@ export function ConfigMenuApp(opts) {
|
|
|
52
53
|
const [editBuf, setEditBuf] = useState('');
|
|
53
54
|
const [pendingReset, setPendingReset] = useState(null);
|
|
54
55
|
const [message, setMessage] = useState(null);
|
|
56
|
+
// Inline palette preview of the theme row ('p').
|
|
57
|
+
const [previewTheme, setPreviewTheme] = useState(false);
|
|
55
58
|
// `get`/`set` mutate the LIVE config object in place, so React has no state
|
|
56
59
|
// change to react to and the row kept the OLD value on screen after a
|
|
57
60
|
// toggle/cycle (the operator saw «ru» while the config already held «en»).
|
|
@@ -216,12 +219,14 @@ export function ConfigMenuApp(opts) {
|
|
|
216
219
|
setCursor((c) => visible.length ? (c - 1 + visible.length) % visible.length : 0);
|
|
217
220
|
setPendingReset(null);
|
|
218
221
|
setMessage(null);
|
|
222
|
+
setPreviewTheme(false);
|
|
219
223
|
return;
|
|
220
224
|
}
|
|
221
225
|
if (key.downArrow || input === 'j') {
|
|
222
226
|
setCursor((c) => (visible.length ? (c + 1) % visible.length : 0));
|
|
223
227
|
setPendingReset(null);
|
|
224
228
|
setMessage(null);
|
|
229
|
+
setPreviewTheme(false);
|
|
225
230
|
return;
|
|
226
231
|
}
|
|
227
232
|
if (input === 'g') {
|
|
@@ -238,6 +243,30 @@ export function ConfigMenuApp(opts) {
|
|
|
238
243
|
setMessage(null);
|
|
239
244
|
return;
|
|
240
245
|
}
|
|
246
|
+
if (input === 'p' || input === 'P') {
|
|
247
|
+
// Inline palette PREVIEW for the theme row: the settings list is where
|
|
248
|
+
// most operators meet ui.theme, and cycling the enum only shows the id.
|
|
249
|
+
// 'p' opens the sample session painted with the CURRENT palette, so the
|
|
250
|
+
// choice can be judged without leaving /config. Other rows: no-op.
|
|
251
|
+
const f = visible[cursor];
|
|
252
|
+
if (f && f.path === 'ui.theme') {
|
|
253
|
+
setPreviewTheme((v) => !v);
|
|
254
|
+
setMessage(null);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (input === 'p' || input === 'P') {
|
|
259
|
+
// Inline palette PREVIEW on the theme row: /config is where most
|
|
260
|
+
// operators meet ui.theme, and cycling the enum only shows the id. 'p'
|
|
261
|
+
// toggles a sample session painted with the CURRENT palette, so the
|
|
262
|
+
// choice can be judged without leaving the menu. Other rows: no-op.
|
|
263
|
+
const f = visible[cursor];
|
|
264
|
+
if (f && f.path === 'ui.theme') {
|
|
265
|
+
setPreviewTheme((v) => !v);
|
|
266
|
+
setMessage(null);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
241
270
|
if (key.return || input === ' ') {
|
|
242
271
|
activate();
|
|
243
272
|
return;
|
|
@@ -336,10 +365,13 @@ export function ConfigMenuApp(opts) {
|
|
|
336
365
|
if (!visible.length) {
|
|
337
366
|
items.push(_jsx(Text, { color: UI.dim, children: ' ' + t('cfg.menu.no_match') }, "none"));
|
|
338
367
|
}
|
|
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: [
|
|
368
|
+
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)) }), previewTheme && visible[cursor]?.path === 'ui.theme' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.dim, children: t('cfg.menu.theme_preview') }), _jsx(ThemePreview, { name: String(get('ui.theme') ?? 'zames'), compact: true })] })) : null, 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: [
|
|
340
369
|
['↑/↓', hint('tui.hint.select')],
|
|
341
370
|
['←/→', hint('tui.hint.change')],
|
|
342
371
|
['Enter', hint('tui.hint.edit')],
|
|
372
|
+
...(visible[cursor]?.path === 'ui.theme'
|
|
373
|
+
? [['p', hint('cfg.menu.theme_preview_key')]]
|
|
374
|
+
: []),
|
|
343
375
|
['/', hint('tui.hint.search')],
|
|
344
376
|
['d', hint('tui.hint.reset')],
|
|
345
377
|
['q', hint('tui.hint.quit')],
|
|
@@ -20,7 +20,7 @@ import { suggestRowCount } from '../input/suggest.js';
|
|
|
20
20
|
import { truncateToWidth, visLen } from '../input/layout.js';
|
|
21
21
|
import { fullScreenLayout } from './layout.js';
|
|
22
22
|
import { useMouseWheel } from './useMouseWheel.js';
|
|
23
|
-
import { UI } from './ui-colors.js';
|
|
23
|
+
import { UI, inputBorderCols } 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);
|
|
@@ -65,6 +65,7 @@ export function FullScreenApp({ store, inputProps, }) {
|
|
|
65
65
|
buf: state.edit.buf,
|
|
66
66
|
cursor: state.edit.cursor,
|
|
67
67
|
suggestRows: suggestRowCount(state.edit.buf, state.commands, cols),
|
|
68
|
+
borderCols: inputBorderCols(),
|
|
68
69
|
});
|
|
69
70
|
const logHeight = fsLayout.logHeight;
|
|
70
71
|
// Window the log BY RENDERED ROW: a long wrapped answer can be taller than
|
package/dist/tui/InputLine.js
CHANGED
|
@@ -16,11 +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
|
+
import { UI, inputBorderStyle } from './ui-colors.js';
|
|
20
20
|
/** Render the visual rows of the buffer with the prompt on the first row. */
|
|
21
21
|
function InputRows({ prompt, buf, cols, }) {
|
|
22
22
|
const lay = layoutInput(prompt, buf, 0, cols);
|
|
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
|
+
return (_jsx(Box, { flexDirection: "column", children: lay.rows.map((r, i) => (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, bold: true, children: r.prefix }), _jsx(Text, { color: UI.text, children: r.text })] }, i))) }));
|
|
24
24
|
}
|
|
25
25
|
export function InputLine(props) {
|
|
26
26
|
const { columns } = useWindowSize();
|
|
@@ -49,7 +49,11 @@ export function InputLine(props) {
|
|
|
49
49
|
// `Math.max(4, cols - 4)` here was one independent copy that could drift from
|
|
50
50
|
// the reservation (the box then rendered more rows than were reserved and the
|
|
51
51
|
// input rows overprinted the status bar).
|
|
52
|
-
|
|
52
|
+
// The frame style comes from ui.inputBorder (set live by /config): round
|
|
53
|
+
// (default), single, double, bold, none. Read per render so a switch applies
|
|
54
|
+
// immediately; 'none' also frees the two border columns for the text.
|
|
55
|
+
const borderStyle = inputBorderStyle();
|
|
56
|
+
const innerCols = inputInnerCols(cols, borderStyle ? 2 : 0);
|
|
53
57
|
// Mirror the state into a ref so the useInput callback (a stable closure Ink
|
|
54
58
|
// calls for every key) always sees the latest values.
|
|
55
59
|
const stRef = useRef(st);
|
|
@@ -321,5 +325,5 @@ export function InputLine(props) {
|
|
|
321
325
|
}), moreCount > 0 && (_jsx(Text, { color: UI.dim, children: ' ' +
|
|
322
326
|
t('editor.more', { n: String(moreCount) }) +
|
|
323
327
|
' ' +
|
|
324
|
-
t('editor.page_hint') }))] })), _jsx(Box, { borderStyle:
|
|
328
|
+
t('editor.page_hint') }))] })), _jsx(Box, { borderStyle: borderStyle, borderColor: props.locked ? UI.dim : UI.accent, paddingLeft: 1, paddingRight: 1, flexDirection: "column", children: _jsx(InputRows, { prompt: props.prompt, buf: st.buf, cols: innerCols }) })] }));
|
|
325
329
|
}
|
package/dist/tui/StatusBar.js
CHANGED
|
@@ -115,12 +115,13 @@ function Badges({ state, budget = Infinity, }) {
|
|
|
115
115
|
}
|
|
116
116
|
return _jsx(_Fragment, { children: all.slice(start).map((b) => b.node) });
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The LEFT part of the status row (spinner/pause/scroll marker) as a string.
|
|
120
|
+
* Extracted from the component so the text and its rules are unit-testable
|
|
121
|
+
* without a terminal; the component only truncates and colorizes it.
|
|
122
|
+
*/
|
|
123
|
+
export function statusLeftText(state, t, frame = 0) {
|
|
121
124
|
const animating = state.busy || !!state.animBase;
|
|
122
|
-
const { frame } = useAnimation({ interval: 400, isActive: animating });
|
|
123
|
-
const t = translate(state.locale);
|
|
124
125
|
const stateLbl = stateLabel(state, state.locale);
|
|
125
126
|
let left;
|
|
126
127
|
if (state.pendingText) {
|
|
@@ -147,20 +148,31 @@ export function StatusBar({ state, divider = true, }) {
|
|
|
147
148
|
// When the log is scrolled up, show a marker in the status line (NOT as a
|
|
148
149
|
// separate row below the input — that pushed the panel up and made the input
|
|
149
150
|
// jump). It replaces the spinner text while scrolled, so it never adds a row.
|
|
151
|
+
// It carries NO timer: the elapsed label belongs to an animation, and a
|
|
152
|
+
// counter running next to «scrolled 5 lines» only ate width.
|
|
150
153
|
const scrollNote = state.scroll > 0
|
|
151
|
-
? translate(state.locale)('status.scrolled', {
|
|
152
|
-
n: String(state.scroll),
|
|
153
|
-
})
|
|
154
|
+
? translate(state.locale)('status.scrolled', { n: String(state.scroll) })
|
|
154
155
|
: '';
|
|
155
|
-
|
|
156
|
+
return scrollNote || prefix + left;
|
|
157
|
+
}
|
|
158
|
+
export function StatusBar({ state, divider = true, }) {
|
|
159
|
+
// Drive the spinner from Ink's shared animation timer (one timer for the
|
|
160
|
+
// whole tree). The interval matches the old 400ms dot cadence.
|
|
161
|
+
const animating = state.busy || !!state.animBase;
|
|
162
|
+
const { frame } = useAnimation({ interval: 400, isActive: animating });
|
|
163
|
+
const t = translate(state.locale);
|
|
164
|
+
const leftText = statusLeftText(state, t, frame);
|
|
156
165
|
const showLeft = !!leftText;
|
|
157
|
-
// Keep the row on ONE line
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
166
|
+
// Keep the row on ONE line: the left status may use whatever the BADGES do
|
|
167
|
+
// not need, minus a small gap so the two never touch. A fixed "a third of
|
|
168
|
+
// the width" budget cut the pause counter mid-number («5…») while half the
|
|
169
|
+
// row sat empty; measuring the badges (they are DROPPED whole when the row
|
|
170
|
+
// is tight, the context counter survives longest) is both accurate and
|
|
171
|
+
// keeps the layout stable.
|
|
162
172
|
const cols = useWindowSize().columns || 80;
|
|
163
|
-
const
|
|
173
|
+
const badgeW = badgeTextWidth(state);
|
|
174
|
+
const gap = badgeW > 0 ? 2 : 0;
|
|
175
|
+
const leftBudget = Math.max(0, cols - 2 - badgeW - gap);
|
|
164
176
|
const leftOut = showLeft ? truncateToWidth(leftText, leftBudget) : '';
|
|
165
177
|
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)) })] })] }));
|
|
166
178
|
}
|
package/dist/tui/ThemePicker.js
CHANGED
|
@@ -10,7 +10,7 @@ import { useState } from 'react';
|
|
|
10
10
|
import { Box, Text, useInput, useApp, useWindowSize } from 'ink';
|
|
11
11
|
import { UI } from './ui-colors.js';
|
|
12
12
|
import { HintBar } from './widgets.js';
|
|
13
|
-
import {
|
|
13
|
+
import { ThemePreview } from './theme-preview.js';
|
|
14
14
|
import { translate } from '../i18n.js';
|
|
15
15
|
export function ThemePicker({ themes, initial, onPreview, locale = 'en', onDone, }) {
|
|
16
16
|
const { exit } = useApp();
|
|
@@ -30,20 +30,6 @@ export function ThemePicker({ themes, initial, onPreview, locale = 'en', onDone,
|
|
|
30
30
|
setCursor(next);
|
|
31
31
|
onPreview(themes[next]);
|
|
32
32
|
};
|
|
33
|
-
// A sample line of every important role, colored through `theme` (chalk):
|
|
34
|
-
// Ink's <Text color> takes a hex STRING, not a formatter, so a gradient like
|
|
35
|
-
// this has to be pre-rendered ANSI.
|
|
36
|
-
const preview = () => [
|
|
37
|
-
theme.user('user'),
|
|
38
|
-
theme.assistant('answer'),
|
|
39
|
-
theme.tool('tool'),
|
|
40
|
-
theme.toolResult('result'),
|
|
41
|
-
theme.system('system'),
|
|
42
|
-
theme.warn('warn'),
|
|
43
|
-
theme.error('error'),
|
|
44
|
-
theme.success('ok'),
|
|
45
|
-
theme.toggleOn('on'),
|
|
46
|
-
].join(' ');
|
|
47
33
|
useInput((input, key) => {
|
|
48
34
|
if (key.escape || (key.ctrl && input === 'c') || input === 'q') {
|
|
49
35
|
finish(null);
|
|
@@ -63,7 +49,7 @@ export function ThemePicker({ themes, initial, onPreview, locale = 'en', onDone,
|
|
|
63
49
|
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: '◆ ' + t('theme.pick_title') }), _jsx(Text, { color: UI.dim, children: themes.length })] }), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), themes.map((name, idx) => {
|
|
64
50
|
const sel = idx === cursor;
|
|
65
51
|
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: name }), idx === start ? (_jsx(Text, { color: UI.dim, children: ' ' + t('tui.list.current') })) : null] }, name));
|
|
66
|
-
}), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), _jsx(
|
|
52
|
+
}), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), _jsx(ThemePreview, { name: themes[cursor] ?? initial, compact: true }), _jsx(HintBar, { hints: [
|
|
67
53
|
['↑/↓', t('theme.pick_move')],
|
|
68
54
|
['Enter', t('tui.hint.ok')],
|
|
69
55
|
['Esc', t('theme.pick_cancel')],
|
package/dist/tui/layout.js
CHANGED
|
@@ -13,7 +13,7 @@ export const HINT_ROWS = 1;
|
|
|
13
13
|
export function fullScreenLayout(input) {
|
|
14
14
|
const termRows = Math.max(1, Math.floor(input.termRows) || 24);
|
|
15
15
|
const cols = Math.max(1, Math.floor(input.cols) || 80);
|
|
16
|
-
const box = inputBoxRows(input.prompt, input.buf, input.cursor, cols);
|
|
16
|
+
const box = inputBoxRows(input.prompt, input.buf, input.cursor, cols, input.borderCols ?? 2);
|
|
17
17
|
const suggestRows = Math.max(0, Math.floor(input.suggestRows) || 0);
|
|
18
18
|
const panelRows = HEADER_ROWS + STATUS_ROWS + box + suggestRows + HINT_ROWS;
|
|
19
19
|
const logHeight = Math.max(1, termRows - panelRows);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { PALETTES, DEFAULT_THEME, isThemeName } from '../theme.js';
|
|
5
|
+
/**
|
|
6
|
+
* The sample lines. Kept PURE DATA so tests can assert the preview covers the
|
|
7
|
+
* important roles and the Ink/plain renderers cannot drift apart.
|
|
8
|
+
*/
|
|
9
|
+
export function previewLines() {
|
|
10
|
+
return [
|
|
11
|
+
{ role: 'dir', text: '~/project' },
|
|
12
|
+
{ role: 'prompt', text: '❯ объясни этот код', bold: true },
|
|
13
|
+
{ role: 'assistant', text: '● Ответ: функция читает файл и парсит JSON.' },
|
|
14
|
+
{ role: 'tool', text: '🔧 Read {"path":"package.json"}' },
|
|
15
|
+
{ role: 'toolResult', text: '→ ✔ 42 строки' },
|
|
16
|
+
{ role: 'taskSummary', text: '· duration: 6s · tools: 1' },
|
|
17
|
+
{ role: 'warn', text: '▲ предупреждение' },
|
|
18
|
+
{ role: 'error', text: '✖ ошибка' },
|
|
19
|
+
{ role: 'success', text: '✔ готово' },
|
|
20
|
+
{ role: 'toggleOn', text: '🌐 поиск вкл' },
|
|
21
|
+
{ role: 'system', text: '● сервисное сообщение' },
|
|
22
|
+
{ role: 'dim', text: '· подсказка' },
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
function paletteOf(name) {
|
|
26
|
+
return PALETTES[isThemeName(name) ? name : DEFAULT_THEME];
|
|
27
|
+
}
|
|
28
|
+
/** The preview as one chalk-painted string (for a plain terminal line). */
|
|
29
|
+
export function themePreviewAnsi(name) {
|
|
30
|
+
const p = paletteOf(name);
|
|
31
|
+
return previewLines()
|
|
32
|
+
.map((l) => {
|
|
33
|
+
const c = chalk.hex(p[l.role]);
|
|
34
|
+
return l.bold ? c.bold(l.text) : c(l.text);
|
|
35
|
+
})
|
|
36
|
+
.join('\n');
|
|
37
|
+
}
|
|
38
|
+
/** The preview as an Ink block (for the picker / the config menu). */
|
|
39
|
+
export function ThemePreview({ name, compact = false, }) {
|
|
40
|
+
const p = paletteOf(name);
|
|
41
|
+
const lines = compact
|
|
42
|
+
? previewLines().filter((l) => ['prompt', 'assistant', 'tool', 'error', 'success', 'dim'].includes(l.role))
|
|
43
|
+
: previewLines();
|
|
44
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((l, i) => (_jsx(Text, { color: p[l.role], bold: l.bold, children: l.text }, i))) }));
|
|
45
|
+
}
|
package/dist/tui/ui-colors.js
CHANGED
|
@@ -26,6 +26,36 @@ export function uiColorsFrom(p) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
export const UI = uiColorsFrom(PALETTES[DEFAULT_THEME]);
|
|
29
|
+
// Border STYLE of the framed surfaces (the input box today). Ink only ships a
|
|
30
|
+
// handful of box styles; 'none' draws no frame at all. Kept next to the colors
|
|
31
|
+
// because it is the same kind of presentation state, and mutated in place so a
|
|
32
|
+
// /config switch applies without re-importing anything.
|
|
33
|
+
let inputBorder = 'round';
|
|
34
|
+
/** Switch the input frame style ('round'|'single'|'double'|'bold'|'none'). */
|
|
35
|
+
export function setInputBorder(name) {
|
|
36
|
+
const v = String(name ?? '')
|
|
37
|
+
.trim()
|
|
38
|
+
.toLowerCase();
|
|
39
|
+
inputBorder =
|
|
40
|
+
v === 'none' ||
|
|
41
|
+
v === 'single' ||
|
|
42
|
+
v === 'double' ||
|
|
43
|
+
v === 'bold' ||
|
|
44
|
+
v === 'round'
|
|
45
|
+
? v
|
|
46
|
+
: 'round';
|
|
47
|
+
return inputBorder;
|
|
48
|
+
}
|
|
49
|
+
/** The current input frame style as an Ink borderStyle value (none = no box). */
|
|
50
|
+
export function inputBorderStyle() {
|
|
51
|
+
if (inputBorder === 'none')
|
|
52
|
+
return undefined;
|
|
53
|
+
return inputBorder;
|
|
54
|
+
}
|
|
55
|
+
/** Border columns the frame consumes (2 = left + right, 0 when off). */
|
|
56
|
+
export function inputBorderCols() {
|
|
57
|
+
return inputBorderStyle() ? 2 : 0;
|
|
58
|
+
}
|
|
29
59
|
/** Repaint the Ink widgets with the palette of `name` (unknown -> default). */
|
|
30
60
|
export function syncUiColors(name) {
|
|
31
61
|
const id = isThemeName(name) ? name : DEFAULT_THEME;
|
package/llms-full.txt
CHANGED
|
@@ -616,7 +616,16 @@ The switch is instant, no restart. `zames` is the default; the bundled palettes
|
|
|
616
616
|
are ports of well-known editor schemes:
|
|
617
617
|
|
|
618
618
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
619
|
-
`solarized-dark`, `tokyo-night
|
|
619
|
+
`solarized-dark`, `tokyo-night`, `catppuccin`, `github-dark`, `ayu`,
|
|
620
|
+
`rose-pine`, `everforest`, `kanagawa`.
|
|
621
|
+
|
|
622
|
+
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
623
|
+
highlighting inside them, the status row and the input frame. To make the choice
|
|
624
|
+
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
625
|
+
below the list, in `/config` press `p` on the theme row); after
|
|
626
|
+
`/theme <id>` the sample is printed too. The frame itself is a separate
|
|
627
|
+
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
628
|
+
(default `round`; `none` removes it).
|
|
620
629
|
|
|
621
630
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
622
631
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.84.0",
|
|
4
4
|
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen or GLM — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"postinstall": "node scripts/postinstall.mjs",
|
|
48
48
|
"prepare": "husky",
|
|
49
49
|
"glm-smoke": "tsx scripts/glm-smoke.mts",
|
|
50
|
-
"seo:topics": "tsx scripts/seo-topics.mts"
|
|
50
|
+
"seo:topics": "tsx scripts/seo-topics.mts",
|
|
51
|
+
"ui-smoke": "tsx scripts/ui-smoke.mts"
|
|
51
52
|
},
|
|
52
53
|
"keywords": [
|
|
53
54
|
"zames",
|