zames_pro 2.83.2 → 2.83.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +9 -0
- package/dist/config.js +8 -0
- package/dist/i18n/config.js +4 -0
- package/dist/index.js +12 -1
- package/dist/input/layout.js +10 -8
- package/dist/tui/FullScreenApp.js +2 -1
- package/dist/tui/InputLine.js +8 -4
- package/dist/tui/StatusBar.js +27 -15
- package/dist/tui/layout.js +1 -1
- package/dist/tui/ui-colors.js +30 -0
- package/llms-full.txt +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.83.4] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **ui:** theme the input text, the status row and the input frame
|
|
15
|
+
|
|
10
16
|
## [2.83.2] - 2026-10-27
|
|
11
17
|
|
|
12
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -633,6 +633,11 @@ are ports of well-known editor schemes:
|
|
|
633
633
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
634
634
|
`solarized-dark`, `tokyo-night`.
|
|
635
635
|
|
|
636
|
+
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
637
|
+
highlighting inside them and the input frame. The frame itself is a separate
|
|
638
|
+
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
639
|
+
(default `round`; `none` removes it).
|
|
640
|
+
|
|
636
641
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
637
642
|
|
|
638
643
|
## FAQ
|
|
@@ -1356,6 +1361,10 @@ enum переключаются на месте; числа и строки от
|
|
|
1356
1361
|
`zames` (по умолчанию), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
1357
1362
|
`solarized-dark`, `tokyo-night`.
|
|
1358
1363
|
|
|
1364
|
+
Тема перекрашивает ВСЁ: интерфейс, ответы агента, подсветку кода в ответах и
|
|
1365
|
+
рамку ввода. Саму рамку можно менять отдельно: `/config set ui.inputBorder
|
|
1366
|
+
<round|single|double|bold|none>` (по умолчанию `round`, `none` — без рамки).
|
|
1367
|
+
|
|
1359
1368
|
Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo,
|
|
1360
1369
|
сессии.
|
|
1361
1370
|
|
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/index.js
CHANGED
|
@@ -5,6 +5,7 @@ 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 { setInputBorder } from './tui/ui-colors.js';
|
|
8
9
|
import { DeepSeekBrowser, headlessUACacheReady } from './browser.js';
|
|
9
10
|
import { QwenBrowser } from './qwen-browser.js';
|
|
10
11
|
import { GlmBrowser } from './glm-browser.js';
|
|
@@ -123,6 +124,10 @@ setAnswerWidth(config.ui?.answerWidth ?? 0);
|
|
|
123
124
|
// banner, startup notices): theme.ts starts on the default and every call site
|
|
124
125
|
// reads the live object, so one apply here repaints the whole session.
|
|
125
126
|
applyTheme(config.ui?.theme);
|
|
127
|
+
// Same for the input-box FRAME style: the TUI reads it per render, so one
|
|
128
|
+
// apply before the editor is created covers the first paint (a /config change
|
|
129
|
+
// goes through setConfigRuntime below).
|
|
130
|
+
setInputBorder(config.ui?.inputBorder);
|
|
126
131
|
// Current interface/agent language. Changed by the /config lang <ru|en> command.
|
|
127
132
|
let currentLocale = isLocale(config.ui?.locale)
|
|
128
133
|
? config.ui.locale
|
|
@@ -1943,6 +1948,11 @@ async function main() {
|
|
|
1943
1948
|
// object is shared and mutated in place, so every later render uses it;
|
|
1944
1949
|
// the editor is asked for a full redraw because the rows already on screen
|
|
1945
1950
|
// (status, prompt, hints) were painted with the OLD colors.
|
|
1951
|
+
if (path === 'ui.inputBorder') {
|
|
1952
|
+
setInputBorder(value);
|
|
1953
|
+
if (editor)
|
|
1954
|
+
editor.setTheme?.(themeColors());
|
|
1955
|
+
}
|
|
1946
1956
|
if (path === 'ui.theme') {
|
|
1947
1957
|
const applied = applyTheme(value);
|
|
1948
1958
|
config.ui.theme = applied;
|
|
@@ -2176,8 +2186,9 @@ async function main() {
|
|
|
2176
2186
|
// read the shared UI map, and without a bump the operator would cycle
|
|
2177
2187
|
// palette ids while the colors stayed those of the previous one.
|
|
2178
2188
|
onAfterChange: (field) => {
|
|
2179
|
-
if (field.path === 'ui.theme')
|
|
2189
|
+
if (field.path === 'ui.theme' || field.path === 'ui.inputBorder') {
|
|
2180
2190
|
editor?.setTheme?.(themeColors());
|
|
2191
|
+
}
|
|
2181
2192
|
},
|
|
2182
2193
|
locale: currentLocale,
|
|
2183
2194
|
};
|
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,
|
|
@@ -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/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);
|
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
|
@@ -618,6 +618,11 @@ are ports of well-known editor schemes:
|
|
|
618
618
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
619
619
|
`solarized-dark`, `tokyo-night`.
|
|
620
620
|
|
|
621
|
+
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
622
|
+
highlighting inside them and the input frame. The frame itself is a separate
|
|
623
|
+
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
624
|
+
(default `round`; `none` removes it).
|
|
625
|
+
|
|
621
626
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
622
627
|
|
|
623
628
|
## FAQ
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.83.
|
|
3
|
+
"version": "2.83.4",
|
|
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": {
|