zames_pro 2.82.142 → 2.83.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 +19 -0
- package/dist/config.js +9 -0
- package/dist/i18n/config.js +4 -0
- package/dist/index.js +24 -1
- package/dist/theme.js +239 -37
- package/dist/tui/AskQuestion.js +2 -2
- package/dist/tui/ConfigMenu.js +7 -1
- package/dist/tui/FullScreenApp.js +7 -3
- package/dist/tui/InputLine.js +5 -4
- package/dist/tui/SelectList.js +1 -1
- package/dist/tui/StatusBar.js +10 -9
- package/dist/tui/TuiEditor.js +9 -0
- package/dist/tui/store.js +1 -0
- package/dist/tui/ui-colors.js +37 -0
- package/dist/tui/widgets.js +5 -14
- package/llms-full.txt +9 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -620,6 +620,15 @@ right away (where possible without a restart).
|
|
|
620
620
|
(help, messages, spinner) and the language the agent answers you in. The
|
|
621
621
|
locale lives in `ui.locale` in the config file.
|
|
622
622
|
|
|
623
|
+
### Themes
|
|
624
|
+
|
|
625
|
+
`/config set ui.theme <id>` (or the `/config` menu, row «Terminal color theme»)
|
|
626
|
+
repaints the whole terminal in a different palette, live — no restart. `zames`
|
|
627
|
+
is the default; the bundled palettes are ports of well-known editor schemes:
|
|
628
|
+
|
|
629
|
+
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
630
|
+
`solarized-dark`, `tokyo-night`.
|
|
631
|
+
|
|
623
632
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
624
633
|
|
|
625
634
|
## FAQ
|
|
@@ -1330,6 +1339,16 @@ enum переключаются на месте; числа и строки от
|
|
|
1330
1339
|
(справка, сообщения, спиннер), и язык, на котором агент вам отвечает. Локаль
|
|
1331
1340
|
хранится в `ui.locale` в файле конфига.
|
|
1332
1341
|
|
|
1342
|
+
### Темы
|
|
1343
|
+
|
|
1344
|
+
`/config set ui.theme <id>` (или меню `/config`, строка «Цветовая тема
|
|
1345
|
+
терминала») перекрашивает весь терминал в другую палитру, сразу — без
|
|
1346
|
+
перезапуска. Тема по умолчанию — `zames`; в комплекте порты известных схем
|
|
1347
|
+
редакторов:
|
|
1348
|
+
|
|
1349
|
+
`zames` (по умолчанию), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
1350
|
+
`solarized-dark`, `tokyo-night`.
|
|
1351
|
+
|
|
1333
1352
|
Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo,
|
|
1334
1353
|
сессии.
|
|
1335
1354
|
|
package/dist/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { DEFAULT_DIAGNOSTICS } from './diagnostics.js';
|
|
|
5
5
|
import { DEFAULT_LOCALE } from './i18n.js';
|
|
6
6
|
import { writeJsonAtomic } from './fsutil.js';
|
|
7
7
|
import { PROVIDER_IDS } from './providers.js';
|
|
8
|
+
import { DEFAULT_THEME, themeNames } from './theme.js';
|
|
8
9
|
const ZAMES_HOME = path.join(os.homedir(), '.zames');
|
|
9
10
|
const HOME_CONFIG = path.join(ZAMES_HOME, 'config.json');
|
|
10
11
|
// The PROJECT config path is resolved LAZILY: `/cd` changes process.cwd() at
|
|
@@ -132,6 +133,7 @@ export const DEFAULTS = {
|
|
|
132
133
|
},
|
|
133
134
|
ui: {
|
|
134
135
|
locale: DEFAULT_LOCALE,
|
|
136
|
+
theme: DEFAULT_THEME,
|
|
135
137
|
contextLimit: 1_000_000,
|
|
136
138
|
// 0 = auto: terminal width, capped at 100 columns (the historical width).
|
|
137
139
|
answerWidth: 0,
|
|
@@ -289,6 +291,13 @@ export const CONFIG_SCHEMA = [
|
|
|
289
291
|
labelKey: 'cfg.f.maxIterations',
|
|
290
292
|
groupKey: 'cfg.group.agent',
|
|
291
293
|
},
|
|
294
|
+
{
|
|
295
|
+
path: 'ui.theme',
|
|
296
|
+
type: 'enum',
|
|
297
|
+
values: themeNames(),
|
|
298
|
+
labelKey: 'cfg.f.ui_theme',
|
|
299
|
+
groupKey: 'cfg.group.ui',
|
|
300
|
+
},
|
|
292
301
|
{
|
|
293
302
|
path: 'ui.answerWidth',
|
|
294
303
|
type: 'number',
|
package/dist/i18n/config.js
CHANGED
|
@@ -55,6 +55,10 @@ export const configMessages = {
|
|
|
55
55
|
ru: 'Язык интерфейса и ответов агента',
|
|
56
56
|
en: 'Interface and agent reply language',
|
|
57
57
|
},
|
|
58
|
+
'cfg.f.ui_theme': {
|
|
59
|
+
ru: 'Цветовая тема терминала',
|
|
60
|
+
en: 'Terminal color theme',
|
|
61
|
+
},
|
|
58
62
|
'cfg.f.ui_answerWidth': {
|
|
59
63
|
ru: 'Ширина контента в колонках: ответы, ввод, инструменты (0 — авто, до 100)',
|
|
60
64
|
en: 'Content width in columns: answers, input, tools (0 = auto, capped at 100)',
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import fs from 'fs/promises';
|
|
5
5
|
import { readFileSync, statSync } from 'fs';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
-
import { theme } from './theme.js';
|
|
7
|
+
import { theme, applyTheme, themeColors } from './theme.js';
|
|
8
8
|
import { DeepSeekBrowser, headlessUACacheReady } from './browser.js';
|
|
9
9
|
import { QwenBrowser } from './qwen-browser.js';
|
|
10
10
|
import { GlmBrowser } from './glm-browser.js';
|
|
@@ -119,6 +119,10 @@ if (providerArg !== null) {
|
|
|
119
119
|
}
|
|
120
120
|
// Apply the configured answer width to the markdown renderer (0 = auto).
|
|
121
121
|
setAnswerWidth(config.ui?.answerWidth ?? 0);
|
|
122
|
+
// The color palette must be selected BEFORE the first line is rendered (the
|
|
123
|
+
// banner, startup notices): theme.ts starts on the default and every call site
|
|
124
|
+
// reads the live object, so one apply here repaints the whole session.
|
|
125
|
+
applyTheme(config.ui?.theme);
|
|
122
126
|
// Current interface/agent language. Changed by the /config lang <ru|en> command.
|
|
123
127
|
let currentLocale = isLocale(config.ui?.locale)
|
|
124
128
|
? config.ui.locale
|
|
@@ -1934,6 +1938,18 @@ async function main() {
|
|
|
1934
1938
|
editor.setPrompt(buildPrompt());
|
|
1935
1939
|
}
|
|
1936
1940
|
}
|
|
1941
|
+
// Repaint the WHOLE terminal in the new palette immediately. The theme
|
|
1942
|
+
// object is shared and mutated in place, so every later render uses it;
|
|
1943
|
+
// the editor is asked for a full redraw because the rows already on screen
|
|
1944
|
+
// (status, prompt, hints) were painted with the OLD colors.
|
|
1945
|
+
if (path === 'ui.theme') {
|
|
1946
|
+
const applied = applyTheme(value);
|
|
1947
|
+
config.ui.theme = applied;
|
|
1948
|
+
if (editor) {
|
|
1949
|
+
editor.setTheme?.(themeColors());
|
|
1950
|
+
editor.refreshStatus();
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1937
1953
|
// Apply a live change to the answer width immediately, so the next
|
|
1938
1954
|
// rendered answer uses the new width without a restart.
|
|
1939
1955
|
if (path === 'ui.answerWidth') {
|
|
@@ -2155,6 +2171,13 @@ async function main() {
|
|
|
2155
2171
|
getDefault: (path) => getByPath(DEFAULTS, path),
|
|
2156
2172
|
set: (field, raw) => configSetRaw(field, raw),
|
|
2157
2173
|
reset: (field) => configResetField(field),
|
|
2174
|
+
// The menu itself must repaint when the THEME row changes: its widgets
|
|
2175
|
+
// read the shared UI map, and without a bump the operator would cycle
|
|
2176
|
+
// palette ids while the colors stayed those of the previous one.
|
|
2177
|
+
onAfterChange: (field) => {
|
|
2178
|
+
if (field.path === 'ui.theme')
|
|
2179
|
+
editor?.setTheme?.(themeColors());
|
|
2180
|
+
},
|
|
2158
2181
|
locale: currentLocale,
|
|
2159
2182
|
};
|
|
2160
2183
|
try {
|
package/dist/theme.js
CHANGED
|
@@ -9,44 +9,246 @@ import { contentWidth } from './width.js';
|
|
|
9
9
|
if (process.env.NO_COLOR) {
|
|
10
10
|
chalk.level = 0;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
12
|
+
export const PALETTES = {
|
|
13
|
+
// zames — the original calm palette (Tokyo Night / Nord spirit): muted,
|
|
14
|
+
// slightly faded tones, comfortable for long answers.
|
|
15
|
+
zames: {
|
|
16
|
+
bg: '#23272e',
|
|
17
|
+
user: '#8fd4f0',
|
|
18
|
+
userBar: '#4a8fb5',
|
|
19
|
+
turnRule: '#3a3f4b',
|
|
20
|
+
turnLabel: '#5b616e',
|
|
21
|
+
prompt: '#c8b06a',
|
|
22
|
+
dir: '#7fc4f0',
|
|
23
|
+
assistant: '#cfc9b0',
|
|
24
|
+
tool: '#c6a97e',
|
|
25
|
+
toolResult: '#8a9bb5',
|
|
26
|
+
system: '#808896',
|
|
27
|
+
dim: '#5b616e',
|
|
28
|
+
taskSummary: '#8fa3c8',
|
|
29
|
+
warn: '#c9a86a',
|
|
30
|
+
error: '#c98a80',
|
|
31
|
+
success: '#a9c08c',
|
|
32
|
+
brown: '#a1723f',
|
|
33
|
+
toggleOn: '#6fd0b0',
|
|
34
|
+
},
|
|
35
|
+
// One Dark — Atom's default theme, the most recognized "modern editor" look.
|
|
36
|
+
'one-dark': {
|
|
37
|
+
bg: '#282c34',
|
|
38
|
+
user: '#61afef',
|
|
39
|
+
userBar: '#3e6f9e',
|
|
40
|
+
turnRule: '#3b4048',
|
|
41
|
+
turnLabel: '#5c6370',
|
|
42
|
+
prompt: '#e5c07b',
|
|
43
|
+
dir: '#61afef',
|
|
44
|
+
assistant: '#abb2bf',
|
|
45
|
+
tool: '#d19a66',
|
|
46
|
+
toolResult: '#7f8b9e',
|
|
47
|
+
system: '#828997',
|
|
48
|
+
dim: '#5c6370',
|
|
49
|
+
taskSummary: '#56b6c2',
|
|
50
|
+
warn: '#e5c07b',
|
|
51
|
+
error: '#e06c75',
|
|
52
|
+
success: '#98c379',
|
|
53
|
+
brown: '#d19a66',
|
|
54
|
+
toggleOn: '#56b6c2',
|
|
55
|
+
},
|
|
56
|
+
// Dracula — high-contrast purple/pink, extremely popular.
|
|
57
|
+
dracula: {
|
|
58
|
+
bg: '#282a36',
|
|
59
|
+
user: '#8be9fd',
|
|
60
|
+
userBar: '#4f8fa6',
|
|
61
|
+
turnRule: '#3d4152',
|
|
62
|
+
turnLabel: '#6272a4',
|
|
63
|
+
prompt: '#f1fa8c',
|
|
64
|
+
dir: '#8be9fd',
|
|
65
|
+
assistant: '#f8f8f2',
|
|
66
|
+
tool: '#ffb86c',
|
|
67
|
+
toolResult: '#b0b8d8',
|
|
68
|
+
system: '#9aa2c0',
|
|
69
|
+
dim: '#6272a4',
|
|
70
|
+
taskSummary: '#8be9fd',
|
|
71
|
+
warn: '#ffb86c',
|
|
72
|
+
error: '#ff5555',
|
|
73
|
+
success: '#50fa7b',
|
|
74
|
+
brown: '#bd93f9',
|
|
75
|
+
toggleOn: '#50fa7b',
|
|
76
|
+
},
|
|
77
|
+
// Gruvbox Dark — warm retro earth tones.
|
|
78
|
+
gruvbox: {
|
|
79
|
+
bg: '#282828',
|
|
80
|
+
user: '#83a598',
|
|
81
|
+
userBar: '#5a7a6e',
|
|
82
|
+
turnRule: '#3c3836',
|
|
83
|
+
turnLabel: '#665c54',
|
|
84
|
+
prompt: '#fabd2f',
|
|
85
|
+
dir: '#83a598',
|
|
86
|
+
assistant: '#ebdbb2',
|
|
87
|
+
tool: '#fe8019',
|
|
88
|
+
toolResult: '#a89984',
|
|
89
|
+
system: '#928374',
|
|
90
|
+
dim: '#665c54',
|
|
91
|
+
taskSummary: '#8ec07c',
|
|
92
|
+
warn: '#fabd2f',
|
|
93
|
+
error: '#fb4934',
|
|
94
|
+
success: '#b8bb26',
|
|
95
|
+
brown: '#d65d0e',
|
|
96
|
+
toggleOn: '#b8bb26',
|
|
97
|
+
},
|
|
98
|
+
// Nord — cool arctic blues, low contrast by design.
|
|
99
|
+
nord: {
|
|
100
|
+
bg: '#2e3440',
|
|
101
|
+
user: '#88c0d0',
|
|
102
|
+
userBar: '#5e81a8',
|
|
103
|
+
turnRule: '#3b4252',
|
|
104
|
+
turnLabel: '#616e88',
|
|
105
|
+
prompt: '#ebcb8b',
|
|
106
|
+
dir: '#88c0d0',
|
|
107
|
+
assistant: '#d8dee9',
|
|
108
|
+
tool: '#d08770',
|
|
109
|
+
toolResult: '#9aa7bd',
|
|
110
|
+
system: '#8b95a8',
|
|
111
|
+
dim: '#616e88',
|
|
112
|
+
taskSummary: '#81a1c1',
|
|
113
|
+
warn: '#ebcb8b',
|
|
114
|
+
error: '#bf616a',
|
|
115
|
+
success: '#a3be8c',
|
|
116
|
+
brown: '#d08770',
|
|
117
|
+
toggleOn: '#8fbcbb',
|
|
118
|
+
},
|
|
119
|
+
// Monokai — the classic Sublime Text scheme.
|
|
120
|
+
monokai: {
|
|
121
|
+
bg: '#272822',
|
|
122
|
+
user: '#66d9ef',
|
|
123
|
+
userBar: '#3f8c9e',
|
|
124
|
+
turnRule: '#3e3d32',
|
|
125
|
+
turnLabel: '#75715e',
|
|
126
|
+
prompt: '#e6db74',
|
|
127
|
+
dir: '#66d9ef',
|
|
128
|
+
assistant: '#f8f8f2',
|
|
129
|
+
tool: '#fd971f',
|
|
130
|
+
toolResult: '#a6a695',
|
|
131
|
+
system: '#a1a19a',
|
|
132
|
+
dim: '#75715e',
|
|
133
|
+
taskSummary: '#66d9ef',
|
|
134
|
+
warn: '#e6db74',
|
|
135
|
+
error: '#f92672',
|
|
136
|
+
success: '#a6e22e',
|
|
137
|
+
brown: '#fd971f',
|
|
138
|
+
toggleOn: '#a6e22e',
|
|
139
|
+
},
|
|
140
|
+
// Solarized Dark — Ethan Schoonover's precision palette.
|
|
141
|
+
'solarized-dark': {
|
|
142
|
+
bg: '#002b36',
|
|
143
|
+
user: '#268bd2',
|
|
144
|
+
userBar: '#1f6a9e',
|
|
145
|
+
turnRule: '#073642',
|
|
146
|
+
turnLabel: '#586e75',
|
|
147
|
+
prompt: '#b58900',
|
|
148
|
+
dir: '#2aa198',
|
|
149
|
+
assistant: '#93a1a1',
|
|
150
|
+
tool: '#cb4b16',
|
|
151
|
+
toolResult: '#839496',
|
|
152
|
+
system: '#839496',
|
|
153
|
+
dim: '#586e75',
|
|
154
|
+
taskSummary: '#2aa198',
|
|
155
|
+
warn: '#b58900',
|
|
156
|
+
error: '#dc322f',
|
|
157
|
+
success: '#859900',
|
|
158
|
+
brown: '#cb4b16',
|
|
159
|
+
toggleOn: '#2aa198',
|
|
160
|
+
},
|
|
161
|
+
// Tokyo Night — the modern neon-on-midnight scheme.
|
|
162
|
+
'tokyo-night': {
|
|
163
|
+
bg: '#1a1b26',
|
|
164
|
+
user: '#7dcfff',
|
|
165
|
+
userBar: '#3d7fa6',
|
|
166
|
+
turnRule: '#2a2e3f',
|
|
167
|
+
turnLabel: '#565f89',
|
|
168
|
+
prompt: '#e0af68',
|
|
169
|
+
dir: '#7dcfff',
|
|
170
|
+
assistant: '#c0caf5',
|
|
171
|
+
tool: '#ff9e64',
|
|
172
|
+
toolResult: '#9aa5ce',
|
|
173
|
+
system: '#787c99',
|
|
174
|
+
dim: '#565f89',
|
|
175
|
+
taskSummary: '#7aa2f7',
|
|
176
|
+
warn: '#e0af68',
|
|
177
|
+
error: '#f7768e',
|
|
178
|
+
success: '#9ece6a',
|
|
179
|
+
brown: '#ff9e64',
|
|
180
|
+
toggleOn: '#73daca',
|
|
181
|
+
},
|
|
49
182
|
};
|
|
183
|
+
/** The palette id used when the configured theme is unknown/missing. */
|
|
184
|
+
export const DEFAULT_THEME = 'zames';
|
|
185
|
+
/**
|
|
186
|
+
* True for a palette id we actually ship. Used by the config layer to
|
|
187
|
+
* normalize `ui.theme` (a stale value from an older/newer build must not
|
|
188
|
+
* leave the operator with an unstyled terminal).
|
|
189
|
+
*/
|
|
190
|
+
export function isThemeName(v) {
|
|
191
|
+
return (typeof v === 'string' && Object.prototype.hasOwnProperty.call(PALETTES, v));
|
|
192
|
+
}
|
|
193
|
+
/** All palette ids, in the order they are offered in /config. */
|
|
194
|
+
export function themeNames() {
|
|
195
|
+
return Object.keys(PALETTES);
|
|
196
|
+
}
|
|
197
|
+
/** Build the chalk formatters for one palette. */
|
|
198
|
+
function buildTheme(p) {
|
|
199
|
+
return {
|
|
200
|
+
user: chalk.hex(p.user),
|
|
201
|
+
userBar: chalk.hex(p.userBar),
|
|
202
|
+
turnRule: chalk.hex(p.turnRule),
|
|
203
|
+
turnLabel: chalk.hex(p.turnLabel),
|
|
204
|
+
prompt: chalk.hex(p.prompt).bold,
|
|
205
|
+
dir: chalk.hex(p.dir),
|
|
206
|
+
assistant: chalk.hex(p.assistant),
|
|
207
|
+
tool: chalk.hex(p.tool),
|
|
208
|
+
toolResult: chalk.hex(p.toolResult),
|
|
209
|
+
system: chalk.hex(p.system),
|
|
210
|
+
dim: chalk.hex(p.dim),
|
|
211
|
+
taskSummary: chalk.hex(p.taskSummary),
|
|
212
|
+
warn: chalk.hex(p.warn),
|
|
213
|
+
error: chalk.hex(p.error),
|
|
214
|
+
success: chalk.hex(p.success),
|
|
215
|
+
brown: chalk.hex(p.brown),
|
|
216
|
+
toggleOn: chalk.hex(p.toggleOn),
|
|
217
|
+
bold: chalk.bold,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// The LIVE theme object. It is mutated IN PLACE by applyTheme() so that every
|
|
221
|
+
// module that captured the import (`import { theme } from './theme.js'`) sees
|
|
222
|
+
// the switch without being re-imported — this is why the call sites read
|
|
223
|
+
// `theme.x` at RENDER time instead of destructuring a color at module load.
|
|
224
|
+
let activeName = DEFAULT_THEME;
|
|
225
|
+
export const theme = buildTheme(PALETTES[DEFAULT_THEME]);
|
|
226
|
+
/** The palette id currently rendered. */
|
|
227
|
+
export function currentThemeName() {
|
|
228
|
+
return activeName;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Switch the palette for every subsequent render. Unknown ids fall back to the
|
|
232
|
+
* default so a hand-edited config can never blank out the colors.
|
|
233
|
+
* Returns the id actually applied.
|
|
234
|
+
*/
|
|
235
|
+
export function applyTheme(name) {
|
|
236
|
+
const next = isThemeName(name) ? name : DEFAULT_THEME;
|
|
237
|
+
const built = buildTheme(PALETTES[next]);
|
|
238
|
+
for (const key of Object.keys(built)) {
|
|
239
|
+
theme[key] = built[key];
|
|
240
|
+
}
|
|
241
|
+
activeName = next;
|
|
242
|
+
return next;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The active palette's RAW hex map — for the Ink TUI, which needs color
|
|
246
|
+
* STRINGS (Ink `<Text color>` does not take chalk functions) and for the
|
|
247
|
+
* README screenshot generator, which renders the real colors as HTML.
|
|
248
|
+
*/
|
|
249
|
+
export function themeColors() {
|
|
250
|
+
return PALETTES[activeName];
|
|
251
|
+
}
|
|
50
252
|
// Horizontal rule that separates the model's answer from the next block. It
|
|
51
253
|
// spans the SHARED content width (src/width.ts) so the rule, the answers and
|
|
52
254
|
// the tool previews all line up at the same margin. The `max` argument is kept
|
package/dist/tui/AskQuestion.js
CHANGED
|
@@ -91,10 +91,10 @@ export function AskQuestion({ question, options, recommended, allowText, locale
|
|
|
91
91
|
out.push(line);
|
|
92
92
|
return out.length ? out : [''];
|
|
93
93
|
};
|
|
94
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.accent, paddingX: 2, children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + t('askuser.title') }), wrap(question).map((line, i) => (_jsx(Text, { color: UI.text, children: line }, i))), _jsx(Text, { color:
|
|
94
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.accent, paddingX: 2, children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + t('askuser.title') }), wrap(question).map((line, i) => (_jsx(Text, { color: UI.text, children: line }, i))), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), typing ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.dim, children: t('askuser.your_answer') }), _jsx(Text, { color: UI.dim, children: t('askuser.enter_send') }), _jsxs(Text, { color: UI.accent, children: ['❯ ', text, '▏'] })] })) : (rows.map((label, i) => {
|
|
95
95
|
const sel = i === cursor;
|
|
96
96
|
const isRec = i === recommended;
|
|
97
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: label }), isRec ? (_jsx(Text, { color:
|
|
97
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: label }), isRec ? (_jsx(Text, { color: UI.on, children: ' (' + t('askuser.recommended') + ')' })) : null] }, i));
|
|
98
98
|
})), _jsx(HintBar, { hints: typing
|
|
99
99
|
? [
|
|
100
100
|
['Enter', t('askuser.hint_send')],
|
package/dist/tui/ConfigMenu.js
CHANGED
|
@@ -39,6 +39,7 @@ function displayValue(field, value, t) {
|
|
|
39
39
|
}
|
|
40
40
|
export function ConfigMenuApp(opts) {
|
|
41
41
|
const { fields, t, get, set, reset, getDefault, locale = 'en' } = opts;
|
|
42
|
+
const { onAfterChange } = opts;
|
|
42
43
|
const { exit } = useApp();
|
|
43
44
|
// The hint bar is operator text: it follows the SAME locale the labels use
|
|
44
45
|
// (a hardcoded Russian bar showed up in an English UI).
|
|
@@ -89,6 +90,7 @@ export function ConfigMenuApp(opts) {
|
|
|
89
90
|
try {
|
|
90
91
|
set(f, raw);
|
|
91
92
|
bump();
|
|
93
|
+
onAfterChange?.(f);
|
|
92
94
|
setMessage(t('cfg.menu.saved', {
|
|
93
95
|
v: f.path,
|
|
94
96
|
value: displayValue(f, get(f.path), t),
|
|
@@ -105,6 +107,7 @@ export function ConfigMenuApp(opts) {
|
|
|
105
107
|
if (f.type === 'boolean') {
|
|
106
108
|
set(f, get(f.path) === true ? 'false' : 'true');
|
|
107
109
|
bump();
|
|
110
|
+
onAfterChange?.(f);
|
|
108
111
|
setMessage(null);
|
|
109
112
|
return;
|
|
110
113
|
}
|
|
@@ -125,6 +128,7 @@ export function ConfigMenuApp(opts) {
|
|
|
125
128
|
const next = f.values[(base + dir + f.values.length) % f.values.length];
|
|
126
129
|
set(f, next);
|
|
127
130
|
bump();
|
|
131
|
+
onAfterChange?.(f);
|
|
128
132
|
setMessage(null);
|
|
129
133
|
};
|
|
130
134
|
/** ←/→ acts on the focused row: cycle an enum, toggle a boolean. */
|
|
@@ -137,6 +141,7 @@ export function ConfigMenuApp(opts) {
|
|
|
137
141
|
else if (f.type === 'boolean') {
|
|
138
142
|
set(f, get(f.path) === true ? 'false' : 'true');
|
|
139
143
|
bump();
|
|
144
|
+
onAfterChange?.(f);
|
|
140
145
|
setMessage(null);
|
|
141
146
|
}
|
|
142
147
|
};
|
|
@@ -244,6 +249,7 @@ export function ConfigMenuApp(opts) {
|
|
|
244
249
|
if (pendingReset === f.path) {
|
|
245
250
|
reset(f);
|
|
246
251
|
bump();
|
|
252
|
+
onAfterChange?.(f);
|
|
247
253
|
setMessage(t('cfg.reset', { v: f.path }));
|
|
248
254
|
setPendingReset(null);
|
|
249
255
|
}
|
|
@@ -330,7 +336,7 @@ export function ConfigMenuApp(opts) {
|
|
|
330
336
|
if (!visible.length) {
|
|
331
337
|
items.push(_jsx(Text, { color: UI.dim, children: ' ' + t('cfg.menu.no_match') }, "none"));
|
|
332
338
|
}
|
|
333
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: UI.title, children: t('cfg.menu.title') }), _jsx(Text, { color: UI.dim, children: (visible.length ? cursor + 1 : 0) + '/' + visible.length })] }), _jsx(Text, { color:
|
|
339
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: UI.title, children: t('cfg.menu.title') }), _jsx(Text, { color: UI.dim, children: (visible.length ? cursor + 1 : 0) + '/' + visible.length })] }), _jsx(Text, { color: UI.border, children: '─'.repeat(Math.max(10, (columns || 80) - 6)) }), items, _jsx(Text, { color: UI.border, children: '─'.repeat(Math.max(10, (columns || 80) - 6)) }), editing ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.text, children: t('cfg.menu.edit_hint') }), _jsx(Text, { color: UI.accent, children: '❯ ' + editBuf })] })) : filterMode ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.text, children: t('cfg.menu.filter_hint') }), _jsx(Text, { color: UI.accent, children: '❯ ' + filterQuery })] })) : message ? (_jsx(Text, { color: UI.ok, children: message })) : filterQuery ? (_jsx(Text, { color: UI.dim, children: t('cfg.menu.filter_active', { q: filterQuery }) })) : null, _jsx(HintBar, { hints: [
|
|
334
340
|
['↑/↓', hint('tui.hint.select')],
|
|
335
341
|
['←/→', hint('tui.hint.change')],
|
|
336
342
|
['Enter', hint('tui.hint.edit')],
|
|
@@ -20,6 +20,7 @@ import { suggestRowCount } from '../input/suggest.js';
|
|
|
20
20
|
import { truncateToWidth, visLen } from '../input/layout.js';
|
|
21
21
|
import { fullScreenLayout } from './layout.js';
|
|
22
22
|
import { useMouseWheel } from './useMouseWheel.js';
|
|
23
|
+
import { UI } from './ui-colors.js';
|
|
23
24
|
/** The header bar: title, version, working directory. */
|
|
24
25
|
function Header({ store }) {
|
|
25
26
|
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
@@ -28,6 +29,9 @@ function Header({ store }) {
|
|
|
28
29
|
.filter(Boolean)
|
|
29
30
|
.join(' ');
|
|
30
31
|
const left = '❯ ' + state.title + (state.version ? ' v' + state.version : '');
|
|
32
|
+
// Referencing the nonce keeps the React dependency honest: the header colors
|
|
33
|
+
// come from the shared UI map, which is mutated in place on a theme switch.
|
|
34
|
+
void state.themeNonce;
|
|
31
35
|
const rawDir = state.workdir ? ' ' + state.workdir : '';
|
|
32
36
|
// The header MUST stay exactly ONE text row: FullScreenApp reserves
|
|
33
37
|
// HEADER_ROWS = 2 (title + divider) and the whole panel height is computed
|
|
@@ -43,7 +47,7 @@ function Header({ store }) {
|
|
|
43
47
|
dir = budget > 0 ? truncateToWidth(dir, budget) : '';
|
|
44
48
|
// If even title + provider exceed the width, drop the directory entirely.
|
|
45
49
|
const finalDir = gap >= 1 ? dir : '';
|
|
46
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color:
|
|
50
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: UI.accent, bold: true, children: left }), _jsx(Text, { color: UI.accent, children: finalDir }), _jsx(Text, { children: ' '.repeat(Math.max(1, cols - visLen(left) - visLen(finalDir) - visLen(right) - 2)) }), right ? _jsx(Text, { color: UI.on, children: right }) : null] }), _jsx(Text, { color: UI.border, children: '─'.repeat(cols) })] }));
|
|
47
51
|
}
|
|
48
52
|
export function FullScreenApp({ store, inputProps, }) {
|
|
49
53
|
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
@@ -107,12 +111,12 @@ export function FullScreenApp({ store, inputProps, }) {
|
|
|
107
111
|
const next = cur + delta * 3;
|
|
108
112
|
store.set({ scroll: Math.max(0, Math.min(maxScroll, next)) });
|
|
109
113
|
}, state.mouseScroll);
|
|
110
|
-
return (_jsxs(Box, { flexDirection: "column", height: termRows, children: [_jsx(Header, { store: store }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 1, overflow: "hidden", justifyContent: "flex-end", children: all.length === 0 ? (_jsx(Text, { color:
|
|
114
|
+
return (_jsxs(Box, { flexDirection: "column", height: termRows, children: [_jsx(Header, { store: store }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, minHeight: 1, overflow: "hidden", justifyContent: "flex-end", children: all.length === 0 ? (_jsx(Text, { color: UI.dim, children: translate(state.locale)('editor.empty_hint') })) : (_jsx(LogList, { entries: visible })) }), _jsx(StatusBar, { state: state, divider: false }), _jsx(InputLine, { ...inputProps, store: store,
|
|
111
115
|
// Cursor rows above the input box: header + log viewport + status.
|
|
112
116
|
// The log pane is FLEXIBLE now, so the panel below it starts right
|
|
113
117
|
// after everything above: header + as many log rows as fit + the
|
|
114
118
|
// suggestion popup + status. `cursorTop` counts the rows ABOVE the
|
|
115
119
|
// input rows; the box border row is added by InputLine.
|
|
116
|
-
cursorTop: fsLayout.cursorTop, prompt: state.prompt, locale: state.locale, commands: state.commands, locked: state.locked }), _jsx(Text, { color:
|
|
120
|
+
cursorTop: fsLayout.cursorTop, prompt: state.prompt, locale: state.locale, commands: state.commands, locked: state.locked }), _jsx(Text, { color: UI.dim, wrap: "truncate", children: ' ' +
|
|
117
121
|
translate(state.locale)(state.mouseScroll ? 'editor.keys_hint' : 'editor.keys_hint_select') })] }));
|
|
118
122
|
}
|
package/dist/tui/InputLine.js
CHANGED
|
@@ -16,10 +16,11 @@ import { insertText, insertPaste, insertNewline, backspace, del, moveLeft, moveR
|
|
|
16
16
|
import { dispatchKey, parseCsiU } from './keys.js';
|
|
17
17
|
import { useSyncExternalStore } from 'react';
|
|
18
18
|
import { matchSlashCommands, suggestWindow, completeSlashCommand, } from '../input/suggest.js';
|
|
19
|
+
import { UI } from './ui-colors.js';
|
|
19
20
|
/** Render the visual rows of the buffer with the prompt on the first row. */
|
|
20
21
|
function InputRows({ prompt, buf, cols, }) {
|
|
21
22
|
const lay = layoutInput(prompt, buf, 0, cols);
|
|
22
|
-
return (_jsx(Box, { flexDirection: "column", children: lay.rows.map((r, i) => (_jsxs(Text, { children: [_jsx(Text, { color:
|
|
23
|
+
return (_jsx(Box, { flexDirection: "column", children: lay.rows.map((r, i) => (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, bold: true, children: r.prefix }), r.text] }, i))) }));
|
|
23
24
|
}
|
|
24
25
|
export function InputLine(props) {
|
|
25
26
|
const { columns } = useWindowSize();
|
|
@@ -316,9 +317,9 @@ export function InputLine(props) {
|
|
|
316
317
|
return (_jsxs(Box, { flexDirection: "column", children: [suggestions.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [shown.map((c, i) => {
|
|
317
318
|
const label = c.hint ? c.name + ' ' + c.hint : c.name;
|
|
318
319
|
const selected = suggestOffset + i === suggestSelected;
|
|
319
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color:
|
|
320
|
-
}), moreCount > 0 && (_jsx(Text, { color:
|
|
320
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: selected ? ' ▸ ' : ' ' }), _jsx(Text, { color: UI.accent, children: label }), _jsx(Text, { color: UI.dim, children: ' ' + c.description })] }, c.name));
|
|
321
|
+
}), moreCount > 0 && (_jsx(Text, { color: UI.dim, children: ' ' +
|
|
321
322
|
t('editor.more', { n: String(moreCount) }) +
|
|
322
323
|
' ' +
|
|
323
|
-
t('editor.page_hint') }))] })), _jsx(Box, { borderStyle: "round", borderColor: props.locked ?
|
|
324
|
+
t('editor.page_hint') }))] })), _jsx(Box, { borderStyle: "round", borderColor: props.locked ? UI.dim : UI.accent, paddingLeft: 1, paddingRight: 1, flexDirection: "column", children: _jsx(InputRows, { prompt: props.prompt, buf: st.buf, cols: innerCols }) })] }));
|
|
324
325
|
}
|
package/dist/tui/SelectList.js
CHANGED
|
@@ -44,7 +44,7 @@ export function SelectList({ title, items, initial, locale = 'en', onSelect, })
|
|
|
44
44
|
finish(items[cursor]?.value ?? null);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + title }), _jsx(Text, { color: UI.dim, children: items.length })] }), _jsx(Text, { color:
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + title }), _jsx(Text, { color: UI.dim, children: items.length })] }), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), items.slice(win.top, win.end).map((it, i) => {
|
|
48
48
|
const idx = win.top + i;
|
|
49
49
|
const sel = idx === cursor;
|
|
50
50
|
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: it.label }), it.detail ? _jsx(Text, { color: UI.dim, children: ' ' + it.detail }) : null] }, it.value));
|
package/dist/tui/StatusBar.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text, useAnimation, useWindowSize } from 'ink';
|
|
3
3
|
import { translate } from '../i18n.js';
|
|
4
4
|
import { DOTS, stripEllipsis } from '../spinner.js';
|
|
5
|
+
import { UI } from './ui-colors.js';
|
|
5
6
|
import { truncateToWidth, visLen } from '../input/layout.js';
|
|
6
7
|
/** Elapsed seconds of the current animated phase ("0s", "45s", "2m 05s"). */
|
|
7
8
|
function elapsedLabel(startedAt, animating) {
|
|
@@ -68,16 +69,16 @@ function Badges({ state, budget = Infinity, }) {
|
|
|
68
69
|
all.push({ key, w, node });
|
|
69
70
|
};
|
|
70
71
|
if (state.statusLine) {
|
|
71
|
-
add('sl', visLen(state.statusLine) + 1, _jsxs(Text, { color:
|
|
72
|
+
add('sl', visLen(state.statusLine) + 1, _jsxs(Text, { color: UI.system, children: [state.statusLine, ' '] }, "sl"));
|
|
72
73
|
}
|
|
73
74
|
if (state.provider) {
|
|
74
|
-
add('prov', visLen('[' + state.provider + '] '), _jsx(Text, { color:
|
|
75
|
+
add('prov', visLen('[' + state.provider + '] '), _jsx(Text, { color: UI.system, children: '[' + state.provider + '] ' }, "prov"));
|
|
75
76
|
}
|
|
76
77
|
if (state.queueLength > 0) {
|
|
77
|
-
add('q', visLen('⧗' + state.queueLength + ' '), _jsxs(Text, { color:
|
|
78
|
+
add('q', visLen('⧗' + state.queueLength + ' '), _jsxs(Text, { color: UI.warn, children: ['⧗' + state.queueLength, ' '] }, "q"));
|
|
78
79
|
}
|
|
79
80
|
if (state.tasks) {
|
|
80
|
-
add('t', visLen(state.tasks) + 1, _jsxs(Text, { color:
|
|
81
|
+
add('t', visLen(state.tasks) + 1, _jsxs(Text, { color: UI.system, children: [state.tasks, ' '] }, "t"));
|
|
81
82
|
}
|
|
82
83
|
const toggles = [];
|
|
83
84
|
if (state.thinkingEnabled)
|
|
@@ -86,14 +87,14 @@ function Badges({ state, budget = Infinity, }) {
|
|
|
86
87
|
toggles.push('🌐\uFE0E');
|
|
87
88
|
if (toggles.length) {
|
|
88
89
|
const label = toggles.join(' ') + ' ';
|
|
89
|
-
add('tg', visLen(label), _jsx(Text, { color:
|
|
90
|
+
add('tg', visLen(label), _jsx(Text, { color: UI.on, children: label }, "tg"));
|
|
90
91
|
}
|
|
91
92
|
if (state.contextText) {
|
|
92
93
|
const color = state.contextLevel === 'high'
|
|
93
|
-
?
|
|
94
|
+
? UI.err
|
|
94
95
|
: state.contextLevel === 'warn'
|
|
95
|
-
?
|
|
96
|
-
:
|
|
96
|
+
? UI.warn
|
|
97
|
+
: UI.ok;
|
|
97
98
|
const marker = state.contextLevel === 'high'
|
|
98
99
|
? '⛔ '
|
|
99
100
|
: state.contextLevel === 'warn'
|
|
@@ -161,5 +162,5 @@ export function StatusBar({ state, divider = true, }) {
|
|
|
161
162
|
const cols = useWindowSize().columns || 80;
|
|
162
163
|
const leftBudget = Math.max(0, Math.min(Math.floor(cols / 3), cols - 14));
|
|
163
164
|
const leftOut = showLeft ? truncateToWidth(leftText, leftBudget) : '';
|
|
164
|
-
return (_jsxs(Box, { flexDirection: "column", children: [divider ? _jsx(Text, { color:
|
|
165
|
+
return (_jsxs(Box, { flexDirection: "column", children: [divider ? _jsx(Text, { color: UI.border, children: '─'.repeat(cols) }) : null, _jsxs(Box, { flexDirection: "row", width: Math.max(1, cols - 2), height: 1, children: [_jsx(Text, { color: UI.accentDim, children: leftOut || ' ' }), _jsx(Box, { flexGrow: 1 }), _jsx(Badges, { state: state, budget: Math.max(0, cols - 2 - visLen(leftOut)) })] })] }));
|
|
165
166
|
}
|
package/dist/tui/TuiEditor.js
CHANGED
|
@@ -344,6 +344,15 @@ export class TuiEditor {
|
|
|
344
344
|
this.locale = locale;
|
|
345
345
|
this.store.set({ locale });
|
|
346
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Repaint the Ink widgets after a palette switch. The hex map (UI) is
|
|
349
|
+
* mutated in place by theme.applyTheme(), but Ink only re-renders when the
|
|
350
|
+
* store emits — bump a nonce so every subscribed component redraws with the
|
|
351
|
+
* new colors.
|
|
352
|
+
*/
|
|
353
|
+
setTheme(_colors) {
|
|
354
|
+
this.store.set({ themeNonce: Date.now() });
|
|
355
|
+
}
|
|
347
356
|
setCommands(commands) {
|
|
348
357
|
this.commands = commands;
|
|
349
358
|
this.store.set({ commands });
|
package/dist/tui/store.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The Ink TUI's color map, derived from the ACTIVE terminal palette
|
|
2
|
+
// (src/theme.ts). Ink `<Text color>` takes a plain hex STRING, not a chalk
|
|
3
|
+
// function, so the TUI cannot reuse `theme` directly — this module is the one
|
|
4
|
+
// bridge between the two.
|
|
5
|
+
//
|
|
6
|
+
// `UI` is mutated IN PLACE by syncUiColors() (like `theme`): every component
|
|
7
|
+
// reads `UI.x` at RENDER time, so a /config theme switch repaints without
|
|
8
|
+
// re-importing anything. Components must never destructure a color at module
|
|
9
|
+
// load — that would freeze the palette at startup.
|
|
10
|
+
import { PALETTES, DEFAULT_THEME, isThemeName } from '../theme.js';
|
|
11
|
+
/** Derive the Ink map from a terminal palette. */
|
|
12
|
+
export function uiColorsFrom(p) {
|
|
13
|
+
return {
|
|
14
|
+
accent: p.prompt,
|
|
15
|
+
accentDim: p.turnLabel,
|
|
16
|
+
text: p.assistant,
|
|
17
|
+
dim: p.dim,
|
|
18
|
+
border: p.turnRule,
|
|
19
|
+
on: p.toggleOn,
|
|
20
|
+
off: p.dim,
|
|
21
|
+
ok: p.success,
|
|
22
|
+
warn: p.warn,
|
|
23
|
+
err: p.error,
|
|
24
|
+
system: p.system,
|
|
25
|
+
title: p.taskSummary,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export const UI = uiColorsFrom(PALETTES[DEFAULT_THEME]);
|
|
29
|
+
/** Repaint the Ink widgets with the palette of `name` (unknown -> default). */
|
|
30
|
+
export function syncUiColors(name) {
|
|
31
|
+
const id = isThemeName(name) ? name : DEFAULT_THEME;
|
|
32
|
+
const next = uiColorsFrom(PALETTES[id]);
|
|
33
|
+
for (const key of Object.keys(next)) {
|
|
34
|
+
UI[key] = next[key];
|
|
35
|
+
}
|
|
36
|
+
return id;
|
|
37
|
+
}
|
package/dist/tui/widgets.js
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
border: '#4a5058',
|
|
9
|
-
on: '#6fd0b0',
|
|
10
|
-
off: '#5b616e',
|
|
11
|
-
ok: '#a9c08c',
|
|
12
|
-
warn: '#c9a86a',
|
|
13
|
-
err: '#c98a80',
|
|
14
|
-
system: '#808896',
|
|
15
|
-
title: '#9fb8d8',
|
|
16
|
-
};
|
|
3
|
+
// The color map lives in ui-colors.ts because it is DERIVED from the active
|
|
4
|
+
// terminal palette and mutated in place on a /config theme switch. Re-exported
|
|
5
|
+
// here so every existing `import { UI } from './widgets.js'` keeps working.
|
|
6
|
+
export { UI } from './ui-colors.js';
|
|
7
|
+
import { UI } from './ui-colors.js';
|
|
17
8
|
/** `[x]` / `[ ]` — a checkbox for a boolean value. */
|
|
18
9
|
export function Checkbox({ checked, focused, }) {
|
|
19
10
|
return (_jsx(Text, { color: checked ? UI.on : UI.off, bold: focused, children: checked ? '[x]' : '[ ]' }));
|
package/llms-full.txt
CHANGED
|
@@ -605,6 +605,15 @@ right away (where possible without a restart).
|
|
|
605
605
|
(help, messages, spinner) and the language the agent answers you in. The
|
|
606
606
|
locale lives in `ui.locale` in the config file.
|
|
607
607
|
|
|
608
|
+
### Themes
|
|
609
|
+
|
|
610
|
+
`/config set ui.theme <id>` (or the `/config` menu, row «Terminal color theme»)
|
|
611
|
+
repaints the whole terminal in a different palette, live — no restart. `zames`
|
|
612
|
+
is the default; the bundled palettes are ports of well-known editor schemes:
|
|
613
|
+
|
|
614
|
+
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
615
|
+
`solarized-dark`, `tokyo-night`.
|
|
616
|
+
|
|
608
617
|
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
609
618
|
|
|
610
619
|
## FAQ
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.83.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": {
|