zames_pro 2.86.0 → 2.87.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 +15 -8
- package/dist/input.js +7 -2
- package/dist/theme-light.js +115 -0
- package/dist/theme.js +12 -5
- package/dist/tui/ConfigMenu.js +6 -2
- package/dist/tui/LogView.js +6 -0
- package/dist/tui/StatusBar.js +76 -70
- package/dist/tui/TuiEditor.js +3 -2
- package/dist/tui/background.js +2 -2
- package/dist/tui/store.js +8 -2
- package/dist/tui/theme-preview.js +2 -2
- package/dist/tui/ui-colors.js +3 -3
- package/dist/tui/widgets.js +7 -1
- package/llms-full.txt +8 -4
- package/package.json +1 -1
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.87.0] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **ui:** light palettes, themed task badge, readable config menu on light themes
|
|
15
|
+
|
|
16
|
+
## [2.86.1] - 2026-10-27
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **ui:** the operator bar on every row, screenshots for all themes
|
|
21
|
+
|
|
10
22
|
## [2.86.0] - 2026-10-27
|
|
11
23
|
|
|
12
24
|
### Added
|
package/README.md
CHANGED
|
@@ -633,7 +633,9 @@ are ports of well-known editor schemes:
|
|
|
633
633
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
634
634
|
`solarized-dark`, `tokyo-night`, `tokyo-storm`, `catppuccin`, `github-dark`,
|
|
635
635
|
`ayu`, `rose-pine`, `everforest`, `kanagawa`, `night-owl`, `material`,
|
|
636
|
-
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2
|
|
636
|
+
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2`,
|
|
637
|
+
plus the LIGHT ones: `one-light`, `github-light`, `solarized-light`,
|
|
638
|
+
`gruvbox-light`, `ayu-light`.
|
|
637
639
|
|
|
638
640
|
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
639
641
|
highlighting inside them, the status row and the input frame. The terminal's
|
|
@@ -643,10 +645,12 @@ Terminal and others; where it is not supported nothing happens). Opt out with
|
|
|
643
645
|
`ZAMES_NO_BG=1` or `NO_COLOR`.
|
|
644
646
|
To make the choice
|
|
645
647
|
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
646
|
-
below the list, in `/config` press `p` on the theme row);
|
|
647
|
-
|
|
648
|
+
below the list, in `/config` press `p` on the theme row); it is not dumped into
|
|
649
|
+
the chat after a pick. Every one of the 29 themes has a screenshot in
|
|
650
|
+
`docs/shots/`. The frame itself is a separate
|
|
648
651
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
649
|
-
(default `round`; `none` removes it).
|
|
652
|
+
(default `round`; `none` removes it). A multi-line operator message keeps its
|
|
653
|
+
`▌` bar on every row.
|
|
650
654
|
|
|
651
655
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
652
656
|
|
|
@@ -1371,7 +1375,9 @@ enum переключаются на месте; числа и строки от
|
|
|
1371
1375
|
`zames` (по умолчанию), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
1372
1376
|
`solarized-dark`, `tokyo-night`, `tokyo-storm`, `catppuccin`, `github-dark`,
|
|
1373
1377
|
`ayu`, `rose-pine`, `everforest`, `kanagawa`, `night-owl`, `material`,
|
|
1374
|
-
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2
|
|
1378
|
+
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2`,
|
|
1379
|
+
а также СВЕТЛЫЕ: `one-light`, `github-light`, `solarized-light`,
|
|
1380
|
+
`gruvbox-light`, `ayu-light`.
|
|
1375
1381
|
|
|
1376
1382
|
Тема перекрашивает ВСЁ: интерфейс, ответы агента, подсветку кода в ответах,
|
|
1377
1383
|
строку статуса и рамку ввода. ФОН окна терминала тоже перекрашивается: тема
|
|
@@ -1380,10 +1386,11 @@ kitty, WezTerm, Alacritty, GNOME Terminal и др.; где не поддержи
|
|
|
1380
1386
|
ничего не происходит). Отключить фон: `ZAMES_NO_BG=1` или `NO_COLOR`.
|
|
1381
1387
|
Чтобы было видно, ЧТО меняется, перед выбором
|
|
1382
1388
|
показывается образец сессии в этой палитре (в `/theme` — под списком, в
|
|
1383
|
-
`/config` — клавиша `p` на строке темы)
|
|
1384
|
-
|
|
1389
|
+
`/config` — клавиша `p` на строке темы); он не печатается в чат после выбора.
|
|
1390
|
+
Все 29 тем есть на скриншотах в `docs/shots/`. Саму рамку можно менять отдельно:
|
|
1385
1391
|
`/config set ui.inputBorder <round|single|double|bold|none>` (по умолчанию
|
|
1386
|
-
`round`, `none` — без рамки).
|
|
1392
|
+
`round`, `none` — без рамки). Рамку (маркер `▌`) у многострочного сообщения
|
|
1393
|
+
пользователя видно на каждой строке.
|
|
1387
1394
|
|
|
1388
1395
|
Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo,
|
|
1389
1396
|
сессии.
|
package/dist/input.js
CHANGED
|
@@ -1192,9 +1192,14 @@ export class LineEditor {
|
|
|
1192
1192
|
// to the browser layer to be attached to the chat.
|
|
1193
1193
|
const attached = this.attachments.items.slice();
|
|
1194
1194
|
// A turn separator + a BAR-marked operator message (same look as the TUI).
|
|
1195
|
+
// The bar goes on EVERY visual row: a long message wrapped to N lines used
|
|
1196
|
+
// to mark only the first one, and the continuation rows looked shifted.
|
|
1195
1197
|
this.printAbove(theme.turnRule('─'.repeat(contentWidth())));
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
+
const wrapped = wrapToWidth(display, contentWidth() - 2);
|
|
1199
|
+
this.printAbove(wrapped
|
|
1200
|
+
.split(String.fromCharCode(10))
|
|
1201
|
+
.map((line) => theme.userBar('▌ ') + theme.user(line))
|
|
1202
|
+
.join(String.fromCharCode(10)));
|
|
1198
1203
|
this.pastes = [];
|
|
1199
1204
|
this.attachments.reset();
|
|
1200
1205
|
if (this.onSubmit)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// The LIGHT palettes, appended to the main palette map. They live in their own
|
|
2
|
+
// module only to keep src/theme.ts readable; the TYPE of a palette and the
|
|
3
|
+
// registry stay there.
|
|
4
|
+
// A dark terminal with a light theme looks broken: these are picked together
|
|
5
|
+
// with the window background (OSC-11), so the whole surface flips. The hues are
|
|
6
|
+
// the LIGHT variants of the corresponding editor schemes (One Light, GitHub
|
|
7
|
+
// Light, Solarized Light, Gruvbox Light, Ayu Light), not a desaturated copy of
|
|
8
|
+
// the dark ones.
|
|
9
|
+
export const LIGHT_PALETTES = {
|
|
10
|
+
// One Light — Atom's light counterpart of One Dark.
|
|
11
|
+
'one-light': {
|
|
12
|
+
bg: '#fafafa',
|
|
13
|
+
user: '#0184bc',
|
|
14
|
+
userBar: '#7fb3cc',
|
|
15
|
+
turnRule: '#e5e5e6',
|
|
16
|
+
turnLabel: '#a0a1a7',
|
|
17
|
+
prompt: '#c18401',
|
|
18
|
+
dir: '#0184bc',
|
|
19
|
+
assistant: '#383a42',
|
|
20
|
+
tool: '#986801',
|
|
21
|
+
toolResult: '#696c77',
|
|
22
|
+
system: '#696c77',
|
|
23
|
+
dim: '#a0a1a7',
|
|
24
|
+
taskSummary: '#4078f2',
|
|
25
|
+
warn: '#c18401',
|
|
26
|
+
error: '#e45649',
|
|
27
|
+
success: '#50a14f',
|
|
28
|
+
brown: '#986801',
|
|
29
|
+
toggleOn: '#0184bc',
|
|
30
|
+
},
|
|
31
|
+
// GitHub Light — GitHub's light UI.
|
|
32
|
+
'github-light': {
|
|
33
|
+
bg: '#ffffff',
|
|
34
|
+
user: '#0969da',
|
|
35
|
+
userBar: '#7ab0e8',
|
|
36
|
+
turnRule: '#eaeef2',
|
|
37
|
+
turnLabel: '#8c959f',
|
|
38
|
+
prompt: '#9a6700',
|
|
39
|
+
dir: '#0550ae',
|
|
40
|
+
assistant: '#1f2328',
|
|
41
|
+
tool: '#bc4c00',
|
|
42
|
+
toolResult: '#57606a',
|
|
43
|
+
system: '#57606a',
|
|
44
|
+
dim: '#8c959f',
|
|
45
|
+
taskSummary: '#0550ae',
|
|
46
|
+
warn: '#9a6700',
|
|
47
|
+
error: '#cf222e',
|
|
48
|
+
success: '#1a7f37',
|
|
49
|
+
brown: '#bc4c00',
|
|
50
|
+
toggleOn: '#0a7ea4',
|
|
51
|
+
},
|
|
52
|
+
// Solarized Light — the light side of Ethan Schoonover's palette.
|
|
53
|
+
'solarized-light': {
|
|
54
|
+
bg: '#fdf6e3',
|
|
55
|
+
user: '#268bd2',
|
|
56
|
+
userBar: '#93b8d4',
|
|
57
|
+
turnRule: '#eee8d5',
|
|
58
|
+
turnLabel: '#93a1a1',
|
|
59
|
+
prompt: '#b58900',
|
|
60
|
+
dir: '#2aa198',
|
|
61
|
+
assistant: '#657b83',
|
|
62
|
+
tool: '#cb4b16',
|
|
63
|
+
toolResult: '#839496',
|
|
64
|
+
system: '#839496',
|
|
65
|
+
dim: '#93a1a1',
|
|
66
|
+
taskSummary: '#2aa198',
|
|
67
|
+
warn: '#b58900',
|
|
68
|
+
error: '#dc322f',
|
|
69
|
+
success: '#859900',
|
|
70
|
+
brown: '#cb4b16',
|
|
71
|
+
toggleOn: '#2aa198',
|
|
72
|
+
},
|
|
73
|
+
// Gruvbox Light — the warm light variant.
|
|
74
|
+
'gruvbox-light': {
|
|
75
|
+
bg: '#fbf1c7',
|
|
76
|
+
user: '#458588',
|
|
77
|
+
userBar: '#8fb5b6',
|
|
78
|
+
turnRule: '#ebdbb2',
|
|
79
|
+
turnLabel: '#a89984',
|
|
80
|
+
prompt: '#b57614',
|
|
81
|
+
dir: '#458588',
|
|
82
|
+
assistant: '#3c3836',
|
|
83
|
+
tool: '#af3a03',
|
|
84
|
+
toolResult: '#7c6f64',
|
|
85
|
+
system: '#7c6f64',
|
|
86
|
+
dim: '#a89984',
|
|
87
|
+
taskSummary: '#79740e',
|
|
88
|
+
warn: '#b57614',
|
|
89
|
+
error: '#9d0006',
|
|
90
|
+
success: '#79740e',
|
|
91
|
+
brown: '#af3a03',
|
|
92
|
+
toggleOn: '#427b58',
|
|
93
|
+
},
|
|
94
|
+
// Ayu Light — the light side of the Ayu scheme.
|
|
95
|
+
'ayu-light': {
|
|
96
|
+
bg: '#fafafa',
|
|
97
|
+
user: '#399ee6',
|
|
98
|
+
userBar: '#9dc8ea',
|
|
99
|
+
turnRule: '#eceef0',
|
|
100
|
+
turnLabel: '#abb0b6',
|
|
101
|
+
prompt: '#fa8d3e',
|
|
102
|
+
dir: '#399ee6',
|
|
103
|
+
assistant: '#5c6166',
|
|
104
|
+
tool: '#f2ae49',
|
|
105
|
+
toolResult: '#787b80',
|
|
106
|
+
system: '#787b80',
|
|
107
|
+
dim: '#abb0b6',
|
|
108
|
+
taskSummary: '#55b4d4',
|
|
109
|
+
warn: '#fa8d3e',
|
|
110
|
+
error: '#f51818',
|
|
111
|
+
success: '#86b300',
|
|
112
|
+
brown: '#f2ae49',
|
|
113
|
+
toggleOn: '#4cbf99',
|
|
114
|
+
},
|
|
115
|
+
};
|
package/dist/theme.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { contentWidth } from './width.js';
|
|
3
3
|
import { applyBackground } from './tui/background.js';
|
|
4
|
+
import { LIGHT_PALETTES } from './theme-light.js';
|
|
4
5
|
// Respect NO_COLOR (https://no-color.org): when the operator sets it (any
|
|
5
6
|
// non-empty value) drop ALL SGR codes so the output can be piped into a file
|
|
6
7
|
// or a log viewer without escape noise. chalk 5 only auto-detects TTY/CI, it
|
|
@@ -519,6 +520,11 @@ export const PALETTES = {
|
|
|
519
520
|
toggleOn: '#7aa89f',
|
|
520
521
|
},
|
|
521
522
|
};
|
|
523
|
+
/** Every palette: the dark ones plus the light variants. */
|
|
524
|
+
export const ALL_PALETTES = {
|
|
525
|
+
...PALETTES,
|
|
526
|
+
...LIGHT_PALETTES,
|
|
527
|
+
};
|
|
522
528
|
/** The palette id used when the configured theme is unknown/missing. */
|
|
523
529
|
export const DEFAULT_THEME = 'zames';
|
|
524
530
|
/**
|
|
@@ -527,11 +533,12 @@ export const DEFAULT_THEME = 'zames';
|
|
|
527
533
|
* leave the operator with an unstyled terminal).
|
|
528
534
|
*/
|
|
529
535
|
export function isThemeName(v) {
|
|
530
|
-
return (typeof v === 'string' &&
|
|
536
|
+
return (typeof v === 'string' &&
|
|
537
|
+
Object.prototype.hasOwnProperty.call(ALL_PALETTES, v));
|
|
531
538
|
}
|
|
532
539
|
/** All palette ids, in the order they are offered in /config. */
|
|
533
540
|
export function themeNames() {
|
|
534
|
-
return Object.keys(
|
|
541
|
+
return Object.keys(ALL_PALETTES);
|
|
535
542
|
}
|
|
536
543
|
/** Build the chalk formatters for one palette. */
|
|
537
544
|
function buildTheme(p) {
|
|
@@ -561,7 +568,7 @@ function buildTheme(p) {
|
|
|
561
568
|
// the switch without being re-imported — this is why the call sites read
|
|
562
569
|
// `theme.x` at RENDER time instead of destructuring a color at module load.
|
|
563
570
|
let activeName = DEFAULT_THEME;
|
|
564
|
-
export const theme = buildTheme(
|
|
571
|
+
export const theme = buildTheme(ALL_PALETTES[DEFAULT_THEME]);
|
|
565
572
|
/** The palette id currently rendered. */
|
|
566
573
|
export function currentThemeName() {
|
|
567
574
|
return activeName;
|
|
@@ -573,7 +580,7 @@ export function currentThemeName() {
|
|
|
573
580
|
*/
|
|
574
581
|
export function applyTheme(name) {
|
|
575
582
|
const next = isThemeName(name) ? name : DEFAULT_THEME;
|
|
576
|
-
const built = buildTheme(
|
|
583
|
+
const built = buildTheme(ALL_PALETTES[next]);
|
|
577
584
|
for (const key of Object.keys(built)) {
|
|
578
585
|
theme[key] = built[key];
|
|
579
586
|
}
|
|
@@ -589,7 +596,7 @@ export function applyTheme(name) {
|
|
|
589
596
|
* README screenshot generator, which renders the real colors as HTML.
|
|
590
597
|
*/
|
|
591
598
|
export function themeColors() {
|
|
592
|
-
return
|
|
599
|
+
return ALL_PALETTES[activeName];
|
|
593
600
|
}
|
|
594
601
|
// Horizontal rule that separates the model's answer from the next block. It
|
|
595
602
|
// spans the SHARED content width (src/width.ts) so the rule, the answers and
|
package/dist/tui/ConfigMenu.js
CHANGED
|
@@ -40,6 +40,10 @@ function displayValue(field, value, t) {
|
|
|
40
40
|
}
|
|
41
41
|
export function ConfigMenuApp(opts) {
|
|
42
42
|
const { fields, t, get, set, reset, getDefault, locale = 'en' } = opts;
|
|
43
|
+
// The menu must be readable on a LIGHT palette too: Ink's Text without an
|
|
44
|
+
// explicit color falls back to the terminal default (often white), which is
|
|
45
|
+
// invisible on a light background. Every row text below is therefore painted
|
|
46
|
+
// with an explicit palette color (UI.text for values, UI.title for groups).
|
|
43
47
|
const { onAfterChange } = opts;
|
|
44
48
|
const { exit } = useApp();
|
|
45
49
|
// The hint bar is operator text: it follows the SAME locale the labels use
|
|
@@ -340,7 +344,7 @@ export function ConfigMenuApp(opts) {
|
|
|
340
344
|
const f = visible[i];
|
|
341
345
|
const g = f.groupKey;
|
|
342
346
|
if (i === shown.start || g !== lastGroup) {
|
|
343
|
-
items.push(_jsx(Text, { color: UI.
|
|
347
|
+
items.push(_jsx(Text, { color: UI.title, children: ' ' + t(f.groupKey).toUpperCase() }, 'g' + i));
|
|
344
348
|
lastGroup = g;
|
|
345
349
|
}
|
|
346
350
|
const selected = i === cursor;
|
|
@@ -360,7 +364,7 @@ export function ConfigMenuApp(opts) {
|
|
|
360
364
|
else {
|
|
361
365
|
valueCell = (_jsx(Text, { color: selected ? UI.text : UI.system, children: displayValue(f, raw, t) }));
|
|
362
366
|
}
|
|
363
|
-
items.push(_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: selected ? '❯ ' : ' ' }), _jsx(Text, { color: selected ? UI.accent :
|
|
367
|
+
items.push(_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: selected ? '❯ ' : ' ' }), _jsx(Text, { color: selected ? UI.accent : UI.text, bold: selected, children: label }), _jsx(Text, { color: UI.dim, children: ' ' }), valueCell, mod ? _jsx(Text, { color: UI.warn, children: ' ' + mod }) : null, selected ? _jsx(Text, { color: UI.dim, children: ' ' + f.path }) : null] }, 'f' + i));
|
|
364
368
|
}
|
|
365
369
|
if (!visible.length) {
|
|
366
370
|
items.push(_jsx(Text, { color: UI.dim, children: ' ' + t('cfg.menu.no_match') }, "none"));
|
package/dist/tui/LogView.js
CHANGED
|
@@ -9,6 +9,12 @@ function LogRow({ entry }) {
|
|
|
9
9
|
// embedded ANSI escape sequences through to the terminal.
|
|
10
10
|
return _jsx(Text, { children: entry.text });
|
|
11
11
|
}
|
|
12
|
+
if (entry.bar) {
|
|
13
|
+
// An operator turn: `▌` on EVERY visual row. Applying the bar to the whole
|
|
14
|
+
// (already wrapped) block keeps the indent equal on continuation lines —
|
|
15
|
+
// prefixing only the first line made a multi-line message look ragged.
|
|
16
|
+
return _jsx(Text, { children: theme.userBar('▌ ') + theme.user(entry.text) });
|
|
17
|
+
}
|
|
12
18
|
switch (entry.kind) {
|
|
13
19
|
case 'user':
|
|
14
20
|
return _jsx(Text, { children: theme.user('❯ ') + entry.text });
|
package/dist/tui/StatusBar.js
CHANGED
|
@@ -24,6 +24,43 @@ function stateLabel(state, locale) {
|
|
|
24
24
|
}
|
|
25
25
|
return '';
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The LEFT part of the status row (spinner/pause/scroll marker) as a string.
|
|
29
|
+
* Extracted from the component so the text and its rules are unit-testable
|
|
30
|
+
* without a terminal; the component only truncates and colorizes it.
|
|
31
|
+
*/
|
|
32
|
+
export function statusLeftText(state, t, frame = 0) {
|
|
33
|
+
const animating = state.busy || !!state.animBase;
|
|
34
|
+
const stateLbl = stateLabel(state, state.locale);
|
|
35
|
+
let left;
|
|
36
|
+
if (state.pendingText) {
|
|
37
|
+
left = '✎ ' + state.pendingText + ' · ' + t('spinner.hint');
|
|
38
|
+
}
|
|
39
|
+
else if (state.animBase) {
|
|
40
|
+
const dots = DOTS[frame % DOTS.length] ?? '';
|
|
41
|
+
const pad = ' '.slice(dots.length);
|
|
42
|
+
left =
|
|
43
|
+
stripEllipsis(state.animBase) +
|
|
44
|
+
dots +
|
|
45
|
+
pad +
|
|
46
|
+
elapsedLabel(state.animStart, animating) +
|
|
47
|
+
' · ' +
|
|
48
|
+
t('spinner.hint');
|
|
49
|
+
}
|
|
50
|
+
else if (state.statusText) {
|
|
51
|
+
left = state.statusText;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
left = '';
|
|
55
|
+
}
|
|
56
|
+
const prefix = stateLbl ? stateLbl + ' ' : '';
|
|
57
|
+
// The scrolled-up marker carries NO timer: the elapsed label belongs to an
|
|
58
|
+
// animation, and a counter running next to «scrolled 5 lines» only ate width.
|
|
59
|
+
const scrollNote = state.scroll > 0
|
|
60
|
+
? translate(state.locale)('status.scrolled', { n: String(state.scroll) })
|
|
61
|
+
: '';
|
|
62
|
+
return scrollNote || prefix + left;
|
|
63
|
+
}
|
|
27
64
|
/** Visible width of the badge row (used to reserve space for the left
|
|
28
65
|
* status). Measured with the shared `visLen` so wide chars (CJK/emoji) count
|
|
29
66
|
* as 2 and the emoji's text-presentation selector (\uFE0E) as 0 — a plain
|
|
@@ -57,103 +94,72 @@ export function badgeTextWidth(state) {
|
|
|
57
94
|
}
|
|
58
95
|
return w;
|
|
59
96
|
}
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
97
|
+
/**
|
|
98
|
+
* The badges to show, in priority order (the LAST survives a narrow terminal).
|
|
99
|
+
* The badge row is what tells the operator WHAT is on — provider, queue, the
|
|
100
|
+
* task counter, the toggles and the context fill — so it is derived here, once,
|
|
101
|
+
* with a palette role each, instead of being hardcoded in the component. The
|
|
102
|
+
* task counter used to be painted with the generic `system` gray, which is why
|
|
103
|
+
* it looked "not themed" next to the provider.
|
|
104
|
+
*/
|
|
105
|
+
export function statusBadges(state) {
|
|
106
|
+
const out = [];
|
|
71
107
|
if (state.statusLine) {
|
|
72
|
-
|
|
108
|
+
out.push({ key: 'sl', label: state.statusLine + ' ', role: 'dim' });
|
|
73
109
|
}
|
|
74
110
|
if (state.provider) {
|
|
75
|
-
|
|
111
|
+
out.push({
|
|
112
|
+
key: 'prov',
|
|
113
|
+
label: '[' + state.provider + '] ',
|
|
114
|
+
role: 'system',
|
|
115
|
+
});
|
|
76
116
|
}
|
|
77
117
|
if (state.queueLength > 0) {
|
|
78
|
-
|
|
118
|
+
out.push({ key: 'q', label: '⧗' + state.queueLength + ' ', role: 'warn' });
|
|
79
119
|
}
|
|
80
120
|
if (state.tasks) {
|
|
81
|
-
|
|
121
|
+
// The task counter is the MODEL's todo progress: paint it with the same
|
|
122
|
+
// role the per-task summary line uses, so it reads as agent activity, not
|
|
123
|
+
// as the generic service gray it had before.
|
|
124
|
+
out.push({ key: 't', label: state.tasks + ' ', role: 'title' });
|
|
82
125
|
}
|
|
83
126
|
const toggles = [];
|
|
84
127
|
if (state.thinkingEnabled)
|
|
85
|
-
toggles.push('
|
|
128
|
+
toggles.push('🧠︎');
|
|
86
129
|
if (state.searchEnabled)
|
|
87
|
-
toggles.push('
|
|
130
|
+
toggles.push('🌐︎');
|
|
88
131
|
if (toggles.length) {
|
|
89
|
-
|
|
90
|
-
add('tg', visLen(label), _jsx(Text, { color: UI.on, children: label }, "tg"));
|
|
132
|
+
out.push({ key: 'tg', label: toggles.join(' ') + ' ', role: 'on' });
|
|
91
133
|
}
|
|
92
134
|
if (state.contextText) {
|
|
93
|
-
const
|
|
94
|
-
?
|
|
135
|
+
const role = state.contextLevel === 'high'
|
|
136
|
+
? 'err'
|
|
95
137
|
: state.contextLevel === 'warn'
|
|
96
|
-
?
|
|
97
|
-
:
|
|
138
|
+
? 'warn'
|
|
139
|
+
: 'ok';
|
|
98
140
|
const marker = state.contextLevel === 'high'
|
|
99
141
|
? '⛔ '
|
|
100
142
|
: state.contextLevel === 'warn'
|
|
101
143
|
? '▲ '
|
|
102
144
|
: '';
|
|
103
|
-
|
|
104
|
-
add('ctx', visLen(label), _jsx(Text, { color: color, children: label }, "ctx"));
|
|
145
|
+
out.push({ key: 'ctx', label: marker + state.contextText, role });
|
|
105
146
|
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
/** The badge row. `budget` caps the row so it never wraps to a second line;
|
|
150
|
+
* when the badges do not fit, the lowest-priority ones (statusLine, then
|
|
151
|
+
* provider) are dropped first so the context counter survives. */
|
|
152
|
+
function Badges({ state, budget = Infinity, }) {
|
|
153
|
+
const all = statusBadges(state);
|
|
106
154
|
if (!all.length)
|
|
107
155
|
return null;
|
|
108
|
-
|
|
109
|
-
// until the row fits the budget. Keep at least the last (context).
|
|
110
|
-
let total = all.reduce((n, b) => n + b.w, 0);
|
|
156
|
+
let total = all.reduce((n, b) => n + visLen(b.label), 0);
|
|
111
157
|
let start = 0;
|
|
112
158
|
while (total > budget && start < all.length - 1) {
|
|
113
|
-
total -= all[start].
|
|
159
|
+
total -= visLen(all[start].label);
|
|
114
160
|
start++;
|
|
115
161
|
}
|
|
116
|
-
return _jsx(_Fragment, { children: all.slice(start).map((b) => b.
|
|
117
|
-
}
|
|
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) {
|
|
124
|
-
const animating = state.busy || !!state.animBase;
|
|
125
|
-
const stateLbl = stateLabel(state, state.locale);
|
|
126
|
-
let left;
|
|
127
|
-
if (state.pendingText) {
|
|
128
|
-
left = '✎ ' + state.pendingText + ' · ' + t('spinner.hint');
|
|
129
|
-
}
|
|
130
|
-
else if (state.animBase) {
|
|
131
|
-
const dots = DOTS[frame % DOTS.length] ?? '';
|
|
132
|
-
const pad = ' '.slice(dots.length);
|
|
133
|
-
left =
|
|
134
|
-
stripEllipsis(state.animBase) +
|
|
135
|
-
dots +
|
|
136
|
-
pad +
|
|
137
|
-
elapsedLabel(state.animStart, animating) +
|
|
138
|
-
' · ' +
|
|
139
|
-
t('spinner.hint');
|
|
140
|
-
}
|
|
141
|
-
else if (state.statusText) {
|
|
142
|
-
left = state.statusText;
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
left = '';
|
|
146
|
-
}
|
|
147
|
-
const prefix = stateLbl ? stateLbl + ' ' : '';
|
|
148
|
-
// When the log is scrolled up, show a marker in the status line (NOT as a
|
|
149
|
-
// separate row below the input — that pushed the panel up and made the input
|
|
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.
|
|
153
|
-
const scrollNote = state.scroll > 0
|
|
154
|
-
? translate(state.locale)('status.scrolled', { n: String(state.scroll) })
|
|
155
|
-
: '';
|
|
156
|
-
return scrollNote || prefix + left;
|
|
162
|
+
return (_jsx(_Fragment, { children: all.slice(start).map((b) => (_jsx(Text, { color: UI[b.role], children: b.label }, b.key))) }));
|
|
157
163
|
}
|
|
158
164
|
export function StatusBar({ state, divider = true, }) {
|
|
159
165
|
// Drive the spinner from Ink's shared animation timer (one timer for the
|
package/dist/tui/TuiEditor.js
CHANGED
|
@@ -258,8 +258,9 @@ export class TuiEditor {
|
|
|
258
258
|
// A turn separator + a BAR-marked, clearly-colored operator message, so an
|
|
259
259
|
// operator turn never blends with the model's answer (operator request).
|
|
260
260
|
this.printAbove(theme.turnRule('─'.repeat(contentWidth())));
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
// The BAR is a per-row marker, drawn by LogView (entry.bar), so every
|
|
262
|
+
// visual row of a long message starts with `▌` instead of only the first.
|
|
263
|
+
this.store.pushLog('user', wrapToWidth(display, contentWidth() - 2), false, { bar: true });
|
|
263
264
|
this.store.set({ pastes: [] });
|
|
264
265
|
// Sending a message pins the log back to the newest content: the operator
|
|
265
266
|
// expects their message (and the answer) at the BOTTOM, so a viewport left
|
package/dist/tui/background.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// The OSC sequence is written STRAIGHT to stdout (not through chalk), because
|
|
13
13
|
// it is not an SGR attribute: `\x1b]11;rgb:rr/gg/bb\x07`.
|
|
14
|
-
import {
|
|
14
|
+
import { ALL_PALETTES, isThemeName, DEFAULT_THEME } from '../theme.js';
|
|
15
15
|
/** `#rrggbb` -> the `rr/gg/bb` part of an OSC-11 payload. */
|
|
16
16
|
function hexToOsc(hex) {
|
|
17
17
|
const h = hex.replace('#', '');
|
|
@@ -26,7 +26,7 @@ export function backgroundOsc(name) {
|
|
|
26
26
|
if (process.env.NO_COLOR || process.env.ZAMES_NO_BG)
|
|
27
27
|
return '';
|
|
28
28
|
const id = isThemeName(name) ? name : DEFAULT_THEME;
|
|
29
|
-
return '\x1b]11;rgb:' + hexToOsc(
|
|
29
|
+
return '\x1b]11;rgb:' + hexToOsc(ALL_PALETTES[id].bg) + '\x07';
|
|
30
30
|
}
|
|
31
31
|
/** Paint the window background for the palette `name` (best effort). */
|
|
32
32
|
export function applyBackground(name) {
|
package/dist/tui/store.js
CHANGED
|
@@ -83,8 +83,14 @@ export class TuiStore {
|
|
|
83
83
|
this.set(fn(this.state));
|
|
84
84
|
}
|
|
85
85
|
/** Append one permanent log entry (an Ink `<Static>` item). */
|
|
86
|
-
pushLog(kind, text, raw = false) {
|
|
87
|
-
const entry = {
|
|
86
|
+
pushLog(kind, text, raw = false, extra) {
|
|
87
|
+
const entry = {
|
|
88
|
+
id: this.nextLogId++,
|
|
89
|
+
kind,
|
|
90
|
+
text,
|
|
91
|
+
raw,
|
|
92
|
+
...extra,
|
|
93
|
+
};
|
|
88
94
|
this.state = { ...this.state, log: [...this.state.log, entry] };
|
|
89
95
|
this.emit();
|
|
90
96
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import {
|
|
4
|
+
import { ALL_PALETTES, DEFAULT_THEME, isThemeName } from '../theme.js';
|
|
5
5
|
/**
|
|
6
6
|
* The sample lines. Kept PURE DATA so tests can assert the preview covers the
|
|
7
7
|
* important roles and the Ink/plain renderers cannot drift apart.
|
|
@@ -23,7 +23,7 @@ export function previewLines() {
|
|
|
23
23
|
];
|
|
24
24
|
}
|
|
25
25
|
function paletteOf(name) {
|
|
26
|
-
return
|
|
26
|
+
return ALL_PALETTES[isThemeName(name) ? name : DEFAULT_THEME];
|
|
27
27
|
}
|
|
28
28
|
/** The preview as one chalk-painted string (for a plain terminal line). */
|
|
29
29
|
export function themePreviewAnsi(name) {
|
package/dist/tui/ui-colors.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// reads `UI.x` at RENDER time, so a /config theme switch repaints without
|
|
8
8
|
// re-importing anything. Components must never destructure a color at module
|
|
9
9
|
// load — that would freeze the palette at startup.
|
|
10
|
-
import {
|
|
10
|
+
import { ALL_PALETTES, DEFAULT_THEME, isThemeName, } from '../theme.js';
|
|
11
11
|
/** Derive the Ink map from a terminal palette. */
|
|
12
12
|
export function uiColorsFrom(p) {
|
|
13
13
|
return {
|
|
@@ -25,7 +25,7 @@ export function uiColorsFrom(p) {
|
|
|
25
25
|
title: p.taskSummary,
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
export const UI = uiColorsFrom(
|
|
28
|
+
export const UI = uiColorsFrom(ALL_PALETTES[DEFAULT_THEME]);
|
|
29
29
|
// Border STYLE of the framed surfaces (the input box today). Ink only ships a
|
|
30
30
|
// handful of box styles; 'none' draws no frame at all. Kept next to the colors
|
|
31
31
|
// because it is the same kind of presentation state, and mutated in place so a
|
|
@@ -59,7 +59,7 @@ export function inputBorderCols() {
|
|
|
59
59
|
/** Repaint the Ink widgets with the palette of `name` (unknown -> default). */
|
|
60
60
|
export function syncUiColors(name) {
|
|
61
61
|
const id = isThemeName(name) ? name : DEFAULT_THEME;
|
|
62
|
-
const next = uiColorsFrom(
|
|
62
|
+
const next = uiColorsFrom(ALL_PALETTES[id]);
|
|
63
63
|
for (const key of Object.keys(next)) {
|
|
64
64
|
UI[key] = next[key];
|
|
65
65
|
}
|
package/dist/tui/widgets.js
CHANGED
|
@@ -7,12 +7,18 @@ export { UI } from './ui-colors.js';
|
|
|
7
7
|
import { UI } from './ui-colors.js';
|
|
8
8
|
/** `[x]` / `[ ]` — a checkbox for a boolean value. */
|
|
9
9
|
export function Checkbox({ checked, focused, }) {
|
|
10
|
+
// A checkbox is ONE unit: `[x]`/`[ ]` must not change color inside the
|
|
11
|
+
// brackets — an unchecked box used to render as two overlapping `[ ]` glyphs
|
|
12
|
+
// because the three parts were painted with different colors.
|
|
10
13
|
return (_jsx(Text, { color: checked ? UI.on : UI.off, bold: focused, children: checked ? '[x]' : '[ ]' }));
|
|
11
14
|
}
|
|
12
15
|
/** `‹ value ›` — an inline SELECT for an enum value. The arrows hint that
|
|
13
16
|
* ←/→ (or Space) cycle the option. */
|
|
14
17
|
export function Select({ value, focused, }) {
|
|
15
|
-
|
|
18
|
+
// A single Text: one SGR run per glyph (`‹` + value + `›`) let some terminals
|
|
19
|
+
// render the arrows as a SECOND row of the same cell, and on a LIGHT palette
|
|
20
|
+
// the dim `off` color made the arrows nearly invisible. One run, one color.
|
|
21
|
+
return (_jsx(Text, { color: focused ? UI.accent : UI.text, bold: focused, children: '‹ ' + value + ' ›' }));
|
|
16
22
|
}
|
|
17
23
|
/** A row of hint chips separated by ` · `. */
|
|
18
24
|
export function HintBar({ hints, }) {
|
package/llms-full.txt
CHANGED
|
@@ -618,7 +618,9 @@ are ports of well-known editor schemes:
|
|
|
618
618
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
619
619
|
`solarized-dark`, `tokyo-night`, `tokyo-storm`, `catppuccin`, `github-dark`,
|
|
620
620
|
`ayu`, `rose-pine`, `everforest`, `kanagawa`, `night-owl`, `material`,
|
|
621
|
-
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2
|
|
621
|
+
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2`,
|
|
622
|
+
plus the LIGHT ones: `one-light`, `github-light`, `solarized-light`,
|
|
623
|
+
`gruvbox-light`, `ayu-light`.
|
|
622
624
|
|
|
623
625
|
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
624
626
|
highlighting inside them, the status row and the input frame. The terminal's
|
|
@@ -628,10 +630,12 @@ Terminal and others; where it is not supported nothing happens). Opt out with
|
|
|
628
630
|
`ZAMES_NO_BG=1` or `NO_COLOR`.
|
|
629
631
|
To make the choice
|
|
630
632
|
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
631
|
-
below the list, in `/config` press `p` on the theme row);
|
|
632
|
-
|
|
633
|
+
below the list, in `/config` press `p` on the theme row); it is not dumped into
|
|
634
|
+
the chat after a pick. Every one of the 29 themes has a screenshot in
|
|
635
|
+
`docs/shots/`. The frame itself is a separate
|
|
633
636
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
634
|
-
(default `round`; `none` removes it).
|
|
637
|
+
(default `round`; `none` removes it). A multi-line operator message keeps its
|
|
638
|
+
`▌` bar on every row.
|
|
635
639
|
|
|
636
640
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
637
641
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.87.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": {
|