zames_pro 2.83.0 → 2.83.1

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,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.83.1] - 2026-10-27
11
+
12
+ ### Added
13
+
14
+ - **ui:** /theme pick list with live preview
15
+
10
16
  ## [2.83.0] - 2026-10-27
11
17
 
12
18
  ### 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;
@@ -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.1",
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": {