zames_pro 2.86.1 → 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 +6 -0
- package/README.md +8 -4
- 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 +4 -2
- 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.87.0] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **ui:** light palettes, themed task badge, readable config menu on light themes
|
|
15
|
+
|
|
10
16
|
## [2.86.1] - 2026-10-27
|
|
11
17
|
|
|
12
18
|
### Fixed
|
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
|
|
@@ -644,7 +646,7 @@ Terminal and others; where it is not supported nothing happens). Opt out with
|
|
|
644
646
|
To make the choice
|
|
645
647
|
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
646
648
|
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
|
|
649
|
+
the chat after a pick. Every one of the 29 themes has a screenshot in
|
|
648
650
|
`docs/shots/`. The frame itself is a separate
|
|
649
651
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
650
652
|
(default `round`; `none` removes it). A multi-line operator message keeps its
|
|
@@ -1373,7 +1375,9 @@ enum переключаются на месте; числа и строки от
|
|
|
1373
1375
|
`zames` (по умолчанию), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
1374
1376
|
`solarized-dark`, `tokyo-night`, `tokyo-storm`, `catppuccin`, `github-dark`,
|
|
1375
1377
|
`ayu`, `rose-pine`, `everforest`, `kanagawa`, `night-owl`, `material`,
|
|
1376
|
-
`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`.
|
|
1377
1381
|
|
|
1378
1382
|
Тема перекрашивает ВСЁ: интерфейс, ответы агента, подсветку кода в ответах,
|
|
1379
1383
|
строку статуса и рамку ввода. ФОН окна терминала тоже перекрашивается: тема
|
|
@@ -1383,7 +1387,7 @@ kitty, WezTerm, Alacritty, GNOME Terminal и др.; где не поддержи
|
|
|
1383
1387
|
Чтобы было видно, ЧТО меняется, перед выбором
|
|
1384
1388
|
показывается образец сессии в этой палитре (в `/theme` — под списком, в
|
|
1385
1389
|
`/config` — клавиша `p` на строке темы); он не печатается в чат после выбора.
|
|
1386
|
-
Все
|
|
1390
|
+
Все 29 тем есть на скриншотах в `docs/shots/`. Саму рамку можно менять отдельно:
|
|
1387
1391
|
`/config set ui.inputBorder <round|single|double|bold|none>` (по умолчанию
|
|
1388
1392
|
`round`, `none` — без рамки). Рамку (маркер `▌`) у многострочного сообщения
|
|
1389
1393
|
пользователя видно на каждой строке.
|
|
@@ -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
|
@@ -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
|
|
@@ -629,7 +631,7 @@ Terminal and others; where it is not supported nothing happens). Opt out with
|
|
|
629
631
|
To make the choice
|
|
630
632
|
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
631
633
|
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
|
|
634
|
+
the chat after a pick. Every one of the 29 themes has a screenshot in
|
|
633
635
|
`docs/shots/`. The frame itself is a separate
|
|
634
636
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
635
637
|
(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.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": {
|