zames_pro 2.83.0 → 2.83.2

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 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.83.2] - 2026-10-27
11
+
12
+ ### Fixed
13
+
14
+ - **ui:** repaint the model answer and the code highlighting on a theme switch
15
+
16
+ ## [2.83.1] - 2026-10-27
17
+
18
+ ### Added
19
+
20
+ - **ui:** /theme pick list with live preview
21
+
10
22
  ## [2.83.0] - 2026-10-27
11
23
 
12
24
  ### Added
package/README.md CHANGED
@@ -622,9 +622,13 @@ locale lives in `ui.locale` in the config file.
622
622
 
623
623
  ### Themes
624
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:
625
+ `/theme` opens a pick list with a LIVE preview: the arrow keys repaint the
626
+ terminal as you move, Enter saves, Esc restores the previous palette.
627
+ `/theme <id>` applies a palette directly, `/theme list` prints every id.
628
+ The same setting is reachable from the settings side:
629
+ `/config set ui.theme <id>` or the `/config` menu («Terminal color theme»).
630
+ The switch is instant, no restart. `zames` is the default; the bundled palettes
631
+ are ports of well-known editor schemes:
628
632
 
629
633
  `zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
630
634
  `solarized-dark`, `tokyo-night`.
@@ -1341,8 +1345,11 @@ enum переключаются на месте; числа и строки от
1341
1345
 
1342
1346
  ### Темы
1343
1347
 
1344
- `/config set ui.theme <id>` (или меню `/config`, строка «Цветовая тема
1345
- терминала») перекрашивает весь терминал в другую палитру, сразу — без
1348
+ `/theme` открывает список тем с живым предпросмотром: стрелками перебираете —
1349
+ терминал сразу перекрашивается, Enter сохраняет, Esc возвращает прежнюю.
1350
+ `/theme <id>` применяет тему сразу, `/theme list` показывает все id.
1351
+ То же самое доступно и через настройки: `/config set ui.theme <id>` или меню
1352
+ `/config`, строка «Цветовая тема терминала». Переключение мгновенное, без
1346
1353
  перезапуска. Тема по умолчанию — `zames`; в комплекте порты известных схем
1347
1354
  редакторов:
1348
1355
 
package/dist/commands.js CHANGED
@@ -1245,6 +1245,7 @@ export const SLASH_COMMANDS = [
1245
1245
  { name: '/queue', key: 'help.cmd.queue' },
1246
1246
  { name: '/tasks', key: 'help.cmd.tasks' },
1247
1247
  { name: '/config', key: 'help.cmd.config' },
1248
+ { name: '/theme', key: 'help.cmd.theme' },
1248
1249
  { name: '/provider', key: 'help.cmd.provider' },
1249
1250
  { name: '/model', key: 'help.cmd.model' },
1250
1251
  { name: '/skills', key: 'help.cmd.skills' },
@@ -59,6 +59,42 @@ export const configMessages = {
59
59
  ru: 'Цветовая тема терминала',
60
60
  en: 'Terminal color theme',
61
61
  },
62
+ 'theme.pick_title': {
63
+ ru: 'Выбор темы',
64
+ en: 'Select a theme',
65
+ },
66
+ 'theme.pick_move': {
67
+ ru: 'примерить тему',
68
+ en: 'preview theme',
69
+ },
70
+ 'theme.pick_cancel': {
71
+ ru: 'отмена (вернуть прежнюю)',
72
+ en: 'cancel (restore)',
73
+ },
74
+ 'theme.set': {
75
+ ru: 'Тема: {v}',
76
+ en: 'Theme: {v}',
77
+ },
78
+ 'theme.restored': {
79
+ ru: 'Тема не изменена: {v}',
80
+ en: 'Theme unchanged: {v}',
81
+ },
82
+ 'theme.unknown': {
83
+ ru: 'Нет такой темы: {v}',
84
+ en: 'No such theme: {v}',
85
+ },
86
+ 'theme.list': {
87
+ ru: 'Темы: {v}',
88
+ en: 'Themes: {v}',
89
+ },
90
+ 'theme.current': {
91
+ ru: 'Текущая тема: {v}',
92
+ en: 'Current theme: {v}',
93
+ },
94
+ 'theme.usage': {
95
+ ru: '/theme — список с выбором; /theme <id> — сразу применить; /theme list — показать id',
96
+ en: '/theme — pick from a list; /theme <id> — apply directly; /theme list — show the ids',
97
+ },
62
98
  'cfg.f.ui_answerWidth': {
63
99
  ru: 'Ширина контента в колонках: ответы, ввод, инструменты (0 — авто, до 100)',
64
100
  en: 'Content width in columns: answers, input, tools (0 = auto, capped at 100)',
package/dist/i18n/help.js CHANGED
@@ -469,6 +469,10 @@ export const helpMessages = {
469
469
  ru: '/config lang <ru|en> сменить язык интерфейса и агента',
470
470
  en: '/config lang <ru|en> switch UI and agent language',
471
471
  },
472
+ 'help.cmd.theme': {
473
+ ru: '/theme [id|list] выбор темы терминала (сразу, без перезапуска)',
474
+ en: '/theme [id|list] pick a terminal color theme (live, no restart)',
475
+ },
472
476
  'help.cmd.provider': {
473
477
  ru: '/provider <id> | status | login — сменить бэкенд (сразу), показать состояние или повторить вход',
474
478
  en: '/provider <id> | status | login — switch the backend (live), show the state or re-run the sign-in',
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, applyTheme, themeColors } from './theme.js';
7
+ import { theme, applyTheme, themeColors, themeNames, currentThemeName, isThemeName, } 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';
@@ -540,6 +540,7 @@ ${theme.bold(t('help.sec.context'))}
540
540
  ${t('help.cmd.init')}
541
541
  ${t('help.cmd.mcp')}
542
542
  ${t('help.cmd.config')}
543
+ ${t('help.cmd.theme')}
543
544
  ${t('help.cmd.lang')}
544
545
 
545
546
  ${theme.bold(t('help.sec.provider'))}
@@ -2198,6 +2199,87 @@ async function main() {
2198
2199
  editor.resume();
2199
2200
  }
2200
2201
  }
2202
+ /**
2203
+ * Apply a palette by id, persist it and repaint the live UI. Shared by
2204
+ * `/theme`, `/config set ui.theme` (through setConfigRuntime) and the picker,
2205
+ * so all three paths behave identically.
2206
+ */
2207
+ function applyThemeById(name) {
2208
+ const applied = applyTheme(name);
2209
+ config.ui.theme = applied;
2210
+ writeConfigValue('home', 'ui.theme', applied);
2211
+ if (editor) {
2212
+ editor.setTheme?.(themeColors());
2213
+ editor.refreshStatus();
2214
+ }
2215
+ return applied;
2216
+ }
2217
+ // `/theme [id|list]` — a direct entry point for the palette, with a pick list
2218
+ // in the TUI. `/config set ui.theme` stays as the scriptable path.
2219
+ async function handleThemeCommand(input) {
2220
+ const arg = input.trim().slice('/theme'.length).trim();
2221
+ const names = themeNames();
2222
+ const emit = (line) => {
2223
+ if (editor)
2224
+ editor.printAbove(line);
2225
+ else
2226
+ console.log(line);
2227
+ };
2228
+ if (arg === 'list' || arg === 'ls') {
2229
+ emit(theme.system(t('theme.list', { v: names.join(', ') }) +
2230
+ String.fromCharCode(10) +
2231
+ t('theme.current', { v: currentThemeName() })));
2232
+ return;
2233
+ }
2234
+ if (arg) {
2235
+ if (!isThemeName(arg)) {
2236
+ emit(theme.error(t('theme.unknown', { v: arg })));
2237
+ emit(theme.dim(t('theme.usage')));
2238
+ return;
2239
+ }
2240
+ emit(theme.assistant(t('theme.set', { v: applyThemeById(arg) })));
2241
+ return;
2242
+ }
2243
+ // No argument: a pick list in the TUI, the id list otherwise. The cursor
2244
+ // applies the palette as a LIVE preview; Esc restores the previous one.
2245
+ if (editor && config.ui.tui) {
2246
+ const before = currentThemeName();
2247
+ editor.pause();
2248
+ let picked;
2249
+ try {
2250
+ const { pickThemeInk } = await import('./tui/run-theme-picker.js');
2251
+ const chosen = await pickThemeInk({
2252
+ themes: names,
2253
+ initial: before,
2254
+ locale: currentLocale,
2255
+ onPreview: (name) => {
2256
+ applyTheme(name);
2257
+ editor?.setTheme?.(themeColors());
2258
+ },
2259
+ });
2260
+ picked = chosen;
2261
+ }
2262
+ catch {
2263
+ picked = null;
2264
+ }
2265
+ finally {
2266
+ editor.resume();
2267
+ }
2268
+ if (picked) {
2269
+ emit(theme.assistant(t('theme.set', { v: applyThemeById(picked) })));
2270
+ }
2271
+ else {
2272
+ // Cancelled: the previews must not stick. Restore WITHOUT persisting.
2273
+ applyTheme(before);
2274
+ editor.setTheme?.(themeColors());
2275
+ emit(theme.system(t('theme.restored', { v: before })));
2276
+ }
2277
+ return;
2278
+ }
2279
+ emit(theme.system(t('theme.list', { v: names.join(', ') }) +
2280
+ String.fromCharCode(10) +
2281
+ t('theme.current', { v: currentThemeName() })));
2282
+ }
2201
2283
  async function handleConfigCommand(input) {
2202
2284
  await runConfigCommand(input, {
2203
2285
  t,
@@ -3489,6 +3571,10 @@ async function main() {
3489
3571
  await handleConfigCommand(trimmed);
3490
3572
  continue;
3491
3573
  }
3574
+ if (lower === '/theme' || lower.startsWith('/theme ')) {
3575
+ await handleThemeCommand(trimmed);
3576
+ continue;
3577
+ }
3492
3578
  if (lower === '/transcript') {
3493
3579
  console.log(theme.system(transcript.file || t('common.off')));
3494
3580
  continue;
package/dist/markdown.js CHANGED
@@ -1,30 +1,73 @@
1
1
  import { render } from 'markdansi';
2
- import { highlight } from 'cli-highlight';
2
+ import { highlight, DEFAULT_THEME as HL_DEFAULT_THEME, plain, } from 'cli-highlight';
3
3
  import supportsHyperlinks from 'supports-hyperlinks';
4
- const theme = {
5
- heading: { color: '#9fb8d8', bold: true },
6
- strong: { color: '#e6e0cc', bold: true },
7
- emph: { color: '#cfc9b0', italic: true },
8
- inlineCode: { color: '#9ecb9e' },
9
- blockCode: { color: '#c9d1c9' },
10
- code: { color: '#9ecb9e' },
11
- link: { color: '#8fb8d8', underline: true },
12
- quote: { color: '#8a8f9a', italic: true },
13
- hr: { color: '#5b616e' },
14
- listMarker: { color: '#8a9bb5' },
15
- tableHeader: { color: '#9fb8d8', bold: true },
16
- tableCell: { color: '#d0d0d0' },
17
- };
18
- function highlighter(code, lang) {
19
- try {
20
- return highlight(code, {
21
- language: lang || 'plaintext',
22
- ignoreIllegals: true,
23
- });
24
- }
25
- catch {
26
- return code;
27
- }
4
+ import { themeColors } from './theme.js';
5
+ /**
6
+ * The Markdown theme for one palette. Rebuilt on every render (it is cheap) so
7
+ * the answers follow a live theme switch; the module no longer freezes the
8
+ * colors at import time.
9
+ */
10
+ function mdTheme(p) {
11
+ return {
12
+ heading: { color: p.taskSummary, bold: true },
13
+ strong: { color: p.assistant, bold: true },
14
+ emph: { color: p.assistant, italic: true },
15
+ inlineCode: { color: p.success },
16
+ blockCode: { color: p.toolResult },
17
+ code: { color: p.success },
18
+ link: { color: p.dir, underline: true },
19
+ quote: { color: p.system, italic: true },
20
+ hr: { color: p.dim },
21
+ listMarker: { color: p.tool },
22
+ tableHeader: { color: p.taskSummary, bold: true },
23
+ tableCell: { color: p.assistant },
24
+ };
25
+ }
26
+ /**
27
+ * Syntax highlighting for a code block, mapped onto the palette. Without this
28
+ * the highlighter used cli-highlight's DEFAULT_THEME (chalk's blue/red/green),
29
+ * which ignores the selected theme entirely and looked off in every non-zames
30
+ * palette. Tokens we do not map fall back to `plain` — NOT to the default
31
+ * theme — so nothing outside the palette can leak in.
32
+ */
33
+ function hlTheme(p) {
34
+ return {
35
+ keyword: (s) => wrap(p.prompt, s),
36
+ built_in: (s) => wrap(p.tool, s),
37
+ type: (s) => wrap(p.taskSummary, s),
38
+ literal: (s) => wrap(p.prompt, s),
39
+ number: (s) => wrap(p.success, s),
40
+ regexp: (s) => wrap(p.error, s),
41
+ string: (s) => wrap(p.success, s),
42
+ comment: (s) => wrap(p.dim, s),
43
+ class: (s) => wrap(p.taskSummary, s),
44
+ function: (s) => wrap(p.dir, s),
45
+ title: (s) => wrap(p.dir, s),
46
+ params: (s) => wrap(p.assistant, s),
47
+ attr: (s) => wrap(p.tool, s),
48
+ variable: (s) => wrap(p.assistant, s),
49
+ doctag: (s) => wrap(p.system, s),
50
+ meta: (s) => wrap(p.system, s),
51
+ tag: (s) => wrap(p.prompt, s),
52
+ name: (s) => wrap(p.prompt, s),
53
+ section: (s) => wrap(p.taskSummary, s),
54
+ link: (s) => wrap(p.dir, s),
55
+ addition: (s) => wrap(p.success, s),
56
+ deletion: (s) => wrap(p.error, s),
57
+ };
58
+ }
59
+ /** Apply a palette color to a highlighted token. */
60
+ function wrap(hex, s) {
61
+ return '\x1b[38;2;' + rgb(hex) + 'm' + s + '\x1b[39m';
62
+ }
63
+ /** `#rrggbb` -> the `r;g;b` part of a truecolor SGR sequence. */
64
+ function rgb(hex) {
65
+ const h = hex.replace('#', '');
66
+ return (parseInt(h.slice(0, 2), 16) +
67
+ ';' +
68
+ parseInt(h.slice(2, 4), 16) +
69
+ ';' +
70
+ parseInt(h.slice(4, 6), 16));
28
71
  }
29
72
  // The answer width is the SHARED content width (src/width.ts), the same one
30
73
  // the operator's echoed text, the tool previews, the warnings and the divider
@@ -43,6 +86,7 @@ export function renderMarkdown(text) {
43
86
  // stay colored while everything else went plain.
44
87
  if (process.env.NO_COLOR)
45
88
  return String(text);
89
+ const p = themeColors();
46
90
  try {
47
91
  const width = contentWidth();
48
92
  return render(String(text), {
@@ -53,8 +97,12 @@ export function renderMarkdown(text) {
53
97
  // looked unformatted). `color: true` makes it always emit SGR; the ONE
54
98
  // opt-out is NO_COLOR, handled above.
55
99
  color: true,
56
- theme: theme,
57
- highlighter,
100
+ theme: mdTheme(p),
101
+ highlighter: (code, lang) => highlight(code, {
102
+ language: lang || 'plaintext',
103
+ ignoreIllegals: true,
104
+ theme: hlTheme(p),
105
+ }),
58
106
  codeBox: true,
59
107
  tableBorder: 'unicode',
60
108
  listIndent: 2,
@@ -68,4 +116,8 @@ export function renderMarkdown(text) {
68
116
  return String(text);
69
117
  }
70
118
  }
119
+ // Keep the import meaningful for tree-shakers/readers: the default theme is
120
+ // deliberately NOT used (see hlTheme), `plain` is the fallback contract.
121
+ void HL_DEFAULT_THEME;
122
+ void plain;
71
123
  export default renderMarkdown;
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // The theme picker — an Ink list of the bundled palettes with a LIVE preview
3
+ // line, opened by /theme (and by the /config menu's theme row).
4
+ //
5
+ // Moving the cursor APPLIES the palette immediately (the preview line and the
6
+ // widget colors repaint), so the operator judges the real look instead of
7
+ // guessing from an id; Esc puts the previous palette back. That is why the
8
+ // preview/restore callbacks are injected — this component stays a pure list.
9
+ import { useState } from 'react';
10
+ import { Box, Text, useInput, useApp, useWindowSize } from 'ink';
11
+ import { UI } from './ui-colors.js';
12
+ import { HintBar } from './widgets.js';
13
+ import { theme } from '../theme.js';
14
+ import { translate } from '../i18n.js';
15
+ export function ThemePicker({ themes, initial, onPreview, locale = 'en', onDone, }) {
16
+ const { exit } = useApp();
17
+ const { columns } = useWindowSize();
18
+ const t = translate(locale);
19
+ const start = Math.max(0, themes.indexOf(initial));
20
+ const [cursor, setCursor] = useState(start);
21
+ const innerCols = Math.max(1, (columns || 80) - 6);
22
+ const finish = (value) => {
23
+ onDone(value);
24
+ exit();
25
+ };
26
+ const move = (dir) => {
27
+ if (!themes.length)
28
+ return;
29
+ const next = (cursor + dir + themes.length) % themes.length;
30
+ setCursor(next);
31
+ onPreview(themes[next]);
32
+ };
33
+ // A sample line of every important role, colored through `theme` (chalk):
34
+ // Ink's <Text color> takes a hex STRING, not a formatter, so a gradient like
35
+ // this has to be pre-rendered ANSI.
36
+ const preview = () => [
37
+ theme.user('user'),
38
+ theme.assistant('answer'),
39
+ theme.tool('tool'),
40
+ theme.toolResult('result'),
41
+ theme.system('system'),
42
+ theme.warn('warn'),
43
+ theme.error('error'),
44
+ theme.success('ok'),
45
+ theme.toggleOn('on'),
46
+ ].join(' ');
47
+ useInput((input, key) => {
48
+ if (key.escape || (key.ctrl && input === 'c') || input === 'q') {
49
+ finish(null);
50
+ return;
51
+ }
52
+ if (key.upArrow || input === 'k') {
53
+ move(-1);
54
+ return;
55
+ }
56
+ if (key.downArrow || input === 'j') {
57
+ move(1);
58
+ return;
59
+ }
60
+ if (key.return)
61
+ finish(themes[cursor] ?? null);
62
+ });
63
+ 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: '◆ ' + t('theme.pick_title') }), _jsx(Text, { color: UI.dim, children: themes.length })] }), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), themes.map((name, idx) => {
64
+ const sel = idx === cursor;
65
+ return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: name }), idx === start ? (_jsx(Text, { color: UI.dim, children: ' ' + t('tui.list.current') })) : null] }, name));
66
+ }), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), _jsx(Text, { children: preview() }), _jsx(HintBar, { hints: [
67
+ ['↑/↓', t('theme.pick_move')],
68
+ ['Enter', t('tui.hint.ok')],
69
+ ['Esc', t('theme.pick_cancel')],
70
+ ] })] }));
71
+ }
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink';
3
+ import { ThemePicker } from './ThemePicker.js';
4
+ export async function pickThemeInk(opts) {
5
+ if (!(process.stdin.isTTY && process.stdout.isTTY))
6
+ return null;
7
+ return await new Promise((resolve) => {
8
+ let done = false;
9
+ const app = render(_jsx(ThemePicker, { ...opts, onDone: (v) => {
10
+ done = true;
11
+ resolve(v);
12
+ } }), { exitOnCtrlC: false });
13
+ app.waitUntilExit().then(() => {
14
+ if (!done)
15
+ resolve(null);
16
+ });
17
+ });
18
+ }
package/llms-full.txt CHANGED
@@ -607,9 +607,13 @@ locale lives in `ui.locale` in the config file.
607
607
 
608
608
  ### Themes
609
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:
610
+ `/theme` opens a pick list with a LIVE preview: the arrow keys repaint the
611
+ terminal as you move, Enter saves, Esc restores the previous palette.
612
+ `/theme <id>` applies a palette directly, `/theme list` prints every id.
613
+ The same setting is reachable from the settings side:
614
+ `/config set ui.theme <id>` or the `/config` menu («Terminal color theme»).
615
+ The switch is instant, no restart. `zames` is the default; the bundled palettes
616
+ are ports of well-known editor schemes:
613
617
 
614
618
  `zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
615
619
  `solarized-dark`, `tokyo-night`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.83.0",
3
+ "version": "2.83.2",
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": {