zames_pro 2.86.1 → 2.88.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 +16 -4
- package/dist/commands.js +12 -0
- package/dist/index.js +6 -0
- package/dist/theme-light.js +115 -0
- package/dist/theme.js +12 -5
- package/dist/tui/ConfigMenu.js +6 -2
- package/dist/tui/StatusBar.js +76 -70
- package/dist/tui/background.js +2 -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 -2
- 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.88.0] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **ui:** switch the theme while the agent is generating
|
|
15
|
+
|
|
16
|
+
## [2.87.0] - 2026-10-27
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **ui:** light palettes, themed task badge, readable config menu on light themes
|
|
21
|
+
|
|
10
22
|
## [2.86.1] - 2026-10-27
|
|
11
23
|
|
|
12
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -625,6 +625,10 @@ locale lives in `ui.locale` in the config file.
|
|
|
625
625
|
`/theme` opens a pick list with a LIVE preview: the arrow keys repaint the
|
|
626
626
|
terminal as you move, Enter saves, Esc restores the previous palette.
|
|
627
627
|
`/theme <id>` applies a palette directly, `/theme list` prints every id.
|
|
628
|
+
The switch also works WHILE the agent is generating: `/theme <id>` and
|
|
629
|
+
`/config set ui.theme <id>` neither interrupt the turn nor touch the chat (it is
|
|
630
|
+
a pure terminal repaint); the bare `/theme` pick list is queued instead, because
|
|
631
|
+
it opens a full-screen list.
|
|
628
632
|
The same setting is reachable from the settings side:
|
|
629
633
|
`/config set ui.theme <id>` or the `/config` menu («Terminal color theme»).
|
|
630
634
|
The switch is instant, no restart. `zames` is the default; the bundled palettes
|
|
@@ -633,7 +637,9 @@ are ports of well-known editor schemes:
|
|
|
633
637
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
634
638
|
`solarized-dark`, `tokyo-night`, `tokyo-storm`, `catppuccin`, `github-dark`,
|
|
635
639
|
`ayu`, `rose-pine`, `everforest`, `kanagawa`, `night-owl`, `material`,
|
|
636
|
-
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2
|
|
640
|
+
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2`,
|
|
641
|
+
plus the LIGHT ones: `one-light`, `github-light`, `solarized-light`,
|
|
642
|
+
`gruvbox-light`, `ayu-light`.
|
|
637
643
|
|
|
638
644
|
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
639
645
|
highlighting inside them, the status row and the input frame. The terminal's
|
|
@@ -644,7 +650,7 @@ Terminal and others; where it is not supported nothing happens). Opt out with
|
|
|
644
650
|
To make the choice
|
|
645
651
|
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
646
652
|
below the list, in `/config` press `p` on the theme row); it is not dumped into
|
|
647
|
-
the chat after a pick. Every one of the
|
|
653
|
+
the chat after a pick. Every one of the 29 themes has a screenshot in
|
|
648
654
|
`docs/shots/`. The frame itself is a separate
|
|
649
655
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
650
656
|
(default `round`; `none` removes it). A multi-line operator message keeps its
|
|
@@ -1365,6 +1371,10 @@ enum переключаются на месте; числа и строки от
|
|
|
1365
1371
|
`/theme` открывает список тем с живым предпросмотром: стрелками перебираете —
|
|
1366
1372
|
терминал сразу перекрашивается, Enter сохраняет, Esc возвращает прежнюю.
|
|
1367
1373
|
`/theme <id>` применяет тему сразу, `/theme list` показывает все id.
|
|
1374
|
+
Переключение работает и ВО ВРЕМЯ работы агента: `/theme <id>` и
|
|
1375
|
+
`/config set ui.theme <id>` не прерывают генерацию и не трогают чат (это чистая
|
|
1376
|
+
перекраска терминала); голый `/theme` со списком ставится в очередь, т.к.
|
|
1377
|
+
открывает полноэкранный список.
|
|
1368
1378
|
То же самое доступно и через настройки: `/config set ui.theme <id>` или меню
|
|
1369
1379
|
`/config`, строка «Цветовая тема терминала». Переключение мгновенное, без
|
|
1370
1380
|
перезапуска. Тема по умолчанию — `zames`; в комплекте порты известных схем
|
|
@@ -1373,7 +1383,9 @@ enum переключаются на месте; числа и строки от
|
|
|
1373
1383
|
`zames` (по умолчанию), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
1374
1384
|
`solarized-dark`, `tokyo-night`, `tokyo-storm`, `catppuccin`, `github-dark`,
|
|
1375
1385
|
`ayu`, `rose-pine`, `everforest`, `kanagawa`, `night-owl`, `material`,
|
|
1376
|
-
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2
|
|
1386
|
+
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2`,
|
|
1387
|
+
а также СВЕТЛЫЕ: `one-light`, `github-light`, `solarized-light`,
|
|
1388
|
+
`gruvbox-light`, `ayu-light`.
|
|
1377
1389
|
|
|
1378
1390
|
Тема перекрашивает ВСЁ: интерфейс, ответы агента, подсветку кода в ответах,
|
|
1379
1391
|
строку статуса и рамку ввода. ФОН окна терминала тоже перекрашивается: тема
|
|
@@ -1383,7 +1395,7 @@ kitty, WezTerm, Alacritty, GNOME Terminal и др.; где не поддержи
|
|
|
1383
1395
|
Чтобы было видно, ЧТО меняется, перед выбором
|
|
1384
1396
|
показывается образец сессии в этой палитре (в `/theme` — под списком, в
|
|
1385
1397
|
`/config` — клавиша `p` на строке темы); он не печатается в чат после выбора.
|
|
1386
|
-
Все
|
|
1398
|
+
Все 29 тем есть на скриншотах в `docs/shots/`. Саму рамку можно менять отдельно:
|
|
1387
1399
|
`/config set ui.inputBorder <round|single|double|bold|none>` (по умолчанию
|
|
1388
1400
|
`round`, `none` — без рамки). Рамку (маркер `▌`) у многострочного сообщения
|
|
1389
1401
|
пользователя видно на каждой строке.
|
package/dist/commands.js
CHANGED
|
@@ -947,6 +947,18 @@ export function missingCommandArgs(names, rest) {
|
|
|
947
947
|
export function isLiveConfigCommand(text) {
|
|
948
948
|
return /^\/config\s+(?!menu\b|ui\b)\S+/i.test(String(text ?? '').trim());
|
|
949
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* `/theme <id>` is safe WHILE the agent works: it only repaints the terminal
|
|
952
|
+
* (the theme object and the Ink colour map are mutated in place) and never
|
|
953
|
+
* touches the browser, the chat or the in-flight generation. So it is
|
|
954
|
+
* intercepted like `/config set` instead of being queued until the task ends.
|
|
955
|
+
*
|
|
956
|
+
* `/theme` with NO argument opens the pick LIST in the TUI, which pauses the
|
|
957
|
+
* editor and would fight the running task — that form stays queued.
|
|
958
|
+
*/
|
|
959
|
+
export function isLiveThemeCommand(text) {
|
|
960
|
+
return /^\/theme\s+\S+/i.test(String(text ?? '').trim());
|
|
961
|
+
}
|
|
950
962
|
// Parse a `/queue` invocation. Returns the subcommand ('list' | 'clear') or
|
|
951
963
|
// null when the text is not a /queue command at all. This runs WHILE the agent
|
|
952
964
|
// is busy (intercepted in the input handler), because the main command loop is
|
package/dist/index.js
CHANGED
|
@@ -1807,6 +1807,12 @@ async function main() {
|
|
|
1807
1807
|
void handleConfigCommand(text);
|
|
1808
1808
|
return;
|
|
1809
1809
|
}
|
|
1810
|
+
// `/theme <id>` is safe mid-run too: a pure repaint. The bare `/theme`
|
|
1811
|
+
// opens the pick list (pauses the editor) and stays queued.
|
|
1812
|
+
if (mod.commands.isLiveThemeCommand(text)) {
|
|
1813
|
+
void handleThemeCommand(text);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1810
1816
|
// `/goal` is safe mid-run: it only sets a variable and writes a file.
|
|
1811
1817
|
if (/^\/goal(\s|$)/i.test(text.trim())) {
|
|
1812
1818
|
void handleGoal(text);
|
|
@@ -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/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/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) {
|
|
@@ -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
|
@@ -610,6 +610,10 @@ locale lives in `ui.locale` in the config file.
|
|
|
610
610
|
`/theme` opens a pick list with a LIVE preview: the arrow keys repaint the
|
|
611
611
|
terminal as you move, Enter saves, Esc restores the previous palette.
|
|
612
612
|
`/theme <id>` applies a palette directly, `/theme list` prints every id.
|
|
613
|
+
The switch also works WHILE the agent is generating: `/theme <id>` and
|
|
614
|
+
`/config set ui.theme <id>` neither interrupt the turn nor touch the chat (it is
|
|
615
|
+
a pure terminal repaint); the bare `/theme` pick list is queued instead, because
|
|
616
|
+
it opens a full-screen list.
|
|
613
617
|
The same setting is reachable from the settings side:
|
|
614
618
|
`/config set ui.theme <id>` or the `/config` menu («Terminal color theme»).
|
|
615
619
|
The switch is instant, no restart. `zames` is the default; the bundled palettes
|
|
@@ -618,7 +622,9 @@ are ports of well-known editor schemes:
|
|
|
618
622
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
619
623
|
`solarized-dark`, `tokyo-night`, `tokyo-storm`, `catppuccin`, `github-dark`,
|
|
620
624
|
`ayu`, `rose-pine`, `everforest`, `kanagawa`, `night-owl`, `material`,
|
|
621
|
-
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2
|
|
625
|
+
`palenight`, `zenburn`, `selenized`, `iceberg`, `sonokai`, `aurora`, `cobalt2`,
|
|
626
|
+
plus the LIGHT ones: `one-light`, `github-light`, `solarized-light`,
|
|
627
|
+
`gruvbox-light`, `ayu-light`.
|
|
622
628
|
|
|
623
629
|
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
624
630
|
highlighting inside them, the status row and the input frame. The terminal's
|
|
@@ -629,7 +635,7 @@ Terminal and others; where it is not supported nothing happens). Opt out with
|
|
|
629
635
|
To make the choice
|
|
630
636
|
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
631
637
|
below the list, in `/config` press `p` on the theme row); it is not dumped into
|
|
632
|
-
the chat after a pick. Every one of the
|
|
638
|
+
the chat after a pick. Every one of the 29 themes has a screenshot in
|
|
633
639
|
`docs/shots/`. The frame itself is a separate
|
|
634
640
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
635
641
|
(default `round`; `none` removes it). A multi-line operator message keeps its
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.88.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": {
|