zames_pro 2.83.4 → 2.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +27 -9
- package/dist/i18n/deepseek.js +8 -0
- package/dist/index.js +11 -5
- package/dist/theme.js +132 -0
- package/dist/tui/ConfigMenu.js +33 -1
- package/dist/tui/InputLine.js +8 -5
- package/dist/tui/ThemePicker.js +2 -16
- package/dist/tui/background.js +42 -0
- package/dist/tui/edit.js +40 -5
- package/dist/tui/theme-preview.js +45 -0
- package/llms-full.txt +12 -3
- package/package.json +3 -2
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.85.0] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **ui:** theme the window background, keep the unsent draft, drop the extra prompt caret
|
|
15
|
+
|
|
16
|
+
## [2.84.0] - 2026-10-27
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **ui:** theme previews, 6 more palettes, inline /config preview
|
|
21
|
+
|
|
10
22
|
## [2.83.4] - 2026-10-27
|
|
11
23
|
|
|
12
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ lock-in. Install with `npm install -g zames_pro`, run `zames` in any project
|
|
|
38
38
|
folder, and describe the task in plain language.
|
|
39
39
|
|
|
40
40
|
<p align="center">
|
|
41
|
-
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/
|
|
41
|
+
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/shots/theme-zames.png" alt="zames terminal coding agent session — an AI coding agent working in the command line without an API key" width="860">
|
|
42
42
|
</p>
|
|
43
43
|
|
|
44
44
|
## Quick start
|
|
@@ -631,10 +631,19 @@ The switch is instant, no restart. `zames` is the default; the bundled palettes
|
|
|
631
631
|
are ports of well-known editor schemes:
|
|
632
632
|
|
|
633
633
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
634
|
-
`solarized-dark`, `tokyo-night
|
|
634
|
+
`solarized-dark`, `tokyo-night`, `catppuccin`, `github-dark`, `ayu`,
|
|
635
|
+
`rose-pine`, `everforest`, `kanagawa`.
|
|
635
636
|
|
|
636
637
|
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
637
|
-
highlighting inside them and the input frame. The
|
|
638
|
+
highlighting inside them, the status row and the input frame. The terminal's
|
|
639
|
+
BACKGROUND follows too: the theme emits OSC-11 with the palette's `bg`
|
|
640
|
+
(supported by iTerm2, Windows Terminal, kitty, WezTerm, Alacritty, GNOME
|
|
641
|
+
Terminal and others; where it is not supported nothing happens). Opt out with
|
|
642
|
+
`ZAMES_NO_BG=1` or `NO_COLOR`.
|
|
643
|
+
To make the choice
|
|
644
|
+
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
645
|
+
below the list, in `/config` press `p` on the theme row); after
|
|
646
|
+
`/theme <id>` the sample is printed too. The frame itself is a separate
|
|
638
647
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
639
648
|
(default `round`; `none` removes it).
|
|
640
649
|
|
|
@@ -757,7 +766,7 @@ Qwen или GLM) в агента для командной строки в ду
|
|
|
757
766
|
можно описать обычным языком.
|
|
758
767
|
|
|
759
768
|
<p align="center">
|
|
760
|
-
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/
|
|
769
|
+
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/shots/theme-zames.png" alt="сессия zames в терминале — терминальный coding-агент и AI-ассистент программиста без API-ключа" width="860">
|
|
761
770
|
</p>
|
|
762
771
|
|
|
763
772
|
## Быстрый старт
|
|
@@ -1359,11 +1368,20 @@ enum переключаются на месте; числа и строки от
|
|
|
1359
1368
|
редакторов:
|
|
1360
1369
|
|
|
1361
1370
|
`zames` (по умолчанию), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
1362
|
-
`solarized-dark`, `tokyo-night
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1371
|
+
`solarized-dark`, `tokyo-night`, `catppuccin`, `github-dark`, `ayu`,
|
|
1372
|
+
`rose-pine`, `everforest`, `kanagawa`.
|
|
1373
|
+
|
|
1374
|
+
Тема перекрашивает ВСЁ: интерфейс, ответы агента, подсветку кода в ответах,
|
|
1375
|
+
строку статуса и рамку ввода. ФОН окна терминала тоже перекрашивается: тема
|
|
1376
|
+
шлёт OSC-11 с цветом `bg` палитры (поддерживают iTerm2, Windows Terminal,
|
|
1377
|
+
kitty, WezTerm, Alacritty, GNOME Terminal и др.; где не поддерживается — просто
|
|
1378
|
+
ничего не происходит). Отключить фон: `ZAMES_NO_BG=1` или `NO_COLOR`.
|
|
1379
|
+
Чтобы было видно, ЧТО меняется, перед выбором
|
|
1380
|
+
показывается образец сессии в этой палитре (в `/theme` — под списком, в
|
|
1381
|
+
`/config` — клавиша `p` на строке темы). После применения `/theme <id>`
|
|
1382
|
+
образец печатается в терминал. Саму рамку можно менять отдельно:
|
|
1383
|
+
`/config set ui.inputBorder <round|single|double|bold|none>` (по умолчанию
|
|
1384
|
+
`round`, `none` — без рамки).
|
|
1367
1385
|
|
|
1368
1386
|
Данные агента хранятся в `~/.zames`: профиль браузера, логи, история undo,
|
|
1369
1387
|
сессии.
|
package/dist/i18n/deepseek.js
CHANGED
|
@@ -212,6 +212,14 @@ export const deepseekMessages = {
|
|
|
212
212
|
ru: '✔ Сохранено: {v} = {value}',
|
|
213
213
|
en: '✔ Saved: {v} = {value}',
|
|
214
214
|
},
|
|
215
|
+
'cfg.menu.theme_preview': {
|
|
216
|
+
ru: 'Так тема выглядит в работе:',
|
|
217
|
+
en: 'How the theme looks in a session:',
|
|
218
|
+
},
|
|
219
|
+
'cfg.menu.theme_preview_key': {
|
|
220
|
+
ru: 'пример темы',
|
|
221
|
+
en: 'theme sample',
|
|
222
|
+
},
|
|
215
223
|
'cfg.menu.default': { ru: 'по умолчанию', en: 'default' },
|
|
216
224
|
'cfg.menu.filter_hint': {
|
|
217
225
|
ru: '/ — поиск по настройкам, d — сбросить (дважды), * — изменено от дефолта',
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from 'fs/promises';
|
|
|
5
5
|
import { readFileSync, statSync } from 'fs';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { theme, applyTheme, themeColors, themeNames, currentThemeName, isThemeName, } from './theme.js';
|
|
8
|
+
import { themePreviewAnsi } from './tui/theme-preview.js';
|
|
8
9
|
import { setInputBorder } from './tui/ui-colors.js';
|
|
9
10
|
import { DeepSeekBrowser, headlessUACacheReady } from './browser.js';
|
|
10
11
|
import { QwenBrowser } from './qwen-browser.js';
|
|
@@ -1518,8 +1519,9 @@ async function main() {
|
|
|
1518
1519
|
theme.dim(') '));
|
|
1519
1520
|
}
|
|
1520
1521
|
// No working-directory tail: it ate input width and is already in the
|
|
1521
|
-
// header / /pwd. A bare
|
|
1522
|
-
|
|
1522
|
+
// header / /pwd. A bare `❯ ` keeps room for the task text and does not
|
|
1523
|
+
// read as a SECOND prompt marker next to the cursor.
|
|
1524
|
+
return theme.prompt('❯ ');
|
|
1523
1525
|
};
|
|
1524
1526
|
await refreshDynamicCommands(currentWorkdir);
|
|
1525
1527
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
@@ -2249,6 +2251,11 @@ async function main() {
|
|
|
2249
2251
|
return;
|
|
2250
2252
|
}
|
|
2251
2253
|
emit(theme.assistant(t('theme.set', { v: applyThemeById(arg) })));
|
|
2254
|
+
// A terminal can only show colors it can paint, and the operator asked
|
|
2255
|
+
// "what exactly changes?" — print a sample of every role in the new
|
|
2256
|
+
// palette right below the confirmation.
|
|
2257
|
+
emit('');
|
|
2258
|
+
emit(themePreviewAnsi(arg));
|
|
2252
2259
|
return;
|
|
2253
2260
|
}
|
|
2254
2261
|
// No argument: a pick list in the TUI, the id list otherwise. The cursor
|
|
@@ -2278,6 +2285,8 @@ async function main() {
|
|
|
2278
2285
|
}
|
|
2279
2286
|
if (picked) {
|
|
2280
2287
|
emit(theme.assistant(t('theme.set', { v: applyThemeById(picked) })));
|
|
2288
|
+
emit('');
|
|
2289
|
+
emit(themePreviewAnsi(picked));
|
|
2281
2290
|
}
|
|
2282
2291
|
else {
|
|
2283
2292
|
// Cancelled: the previews must not stick. Restore WITHOUT persisting.
|
|
@@ -2438,9 +2447,6 @@ async function main() {
|
|
|
2438
2447
|
theme.dir(reviewMode.snapName) +
|
|
2439
2448
|
theme.dim(') ');
|
|
2440
2449
|
}
|
|
2441
|
-
else {
|
|
2442
|
-
tail = theme.dim('› ');
|
|
2443
|
-
}
|
|
2444
2450
|
input = await promptOnce(theme.prompt('❯ ') + tail);
|
|
2445
2451
|
}
|
|
2446
2452
|
}
|
package/dist/theme.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { contentWidth } from './width.js';
|
|
3
|
+
import { applyBackground } from './tui/background.js';
|
|
3
4
|
// Respect NO_COLOR (https://no-color.org): when the operator sets it (any
|
|
4
5
|
// non-empty value) drop ALL SGR codes so the output can be piped into a file
|
|
5
6
|
// or a log viewer without escape noise. chalk 5 only auto-detects TTY/CI, it
|
|
@@ -179,6 +180,134 @@ export const PALETTES = {
|
|
|
179
180
|
brown: '#ff9e64',
|
|
180
181
|
toggleOn: '#73daca',
|
|
181
182
|
},
|
|
183
|
+
// Catppuccin Mocha — the soft pastel palette that took over the editor
|
|
184
|
+
// ecosystem (Neovim, VS Code, terminals).
|
|
185
|
+
catppuccin: {
|
|
186
|
+
bg: '#1e1e2e',
|
|
187
|
+
user: '#89b4fa',
|
|
188
|
+
userBar: '#5477a8',
|
|
189
|
+
turnRule: '#313244',
|
|
190
|
+
turnLabel: '#6c7086',
|
|
191
|
+
prompt: '#f9e2af',
|
|
192
|
+
dir: '#89dceb',
|
|
193
|
+
assistant: '#cdd6f4',
|
|
194
|
+
tool: '#fab387',
|
|
195
|
+
toolResult: '#a6adc8',
|
|
196
|
+
system: '#9399b2',
|
|
197
|
+
dim: '#6c7086',
|
|
198
|
+
taskSummary: '#89b4fa',
|
|
199
|
+
warn: '#f9e2af',
|
|
200
|
+
error: '#f38ba8',
|
|
201
|
+
success: '#a6e3a1',
|
|
202
|
+
brown: '#eba0ac',
|
|
203
|
+
toggleOn: '#94e2d5',
|
|
204
|
+
},
|
|
205
|
+
// GitHub Dark — the palette of GitHub's dark UI (familiar to anyone who
|
|
206
|
+
// reads diffs all day).
|
|
207
|
+
'github-dark': {
|
|
208
|
+
bg: '#0d1117',
|
|
209
|
+
user: '#58a6ff',
|
|
210
|
+
userBar: '#3a6ea8',
|
|
211
|
+
turnRule: '#21262d',
|
|
212
|
+
turnLabel: '#6e7681',
|
|
213
|
+
prompt: '#d29922',
|
|
214
|
+
dir: '#79c0ff',
|
|
215
|
+
assistant: '#c9d1d9',
|
|
216
|
+
tool: '#ffa657',
|
|
217
|
+
toolResult: '#8b949e',
|
|
218
|
+
system: '#8b949e',
|
|
219
|
+
dim: '#6e7681',
|
|
220
|
+
taskSummary: '#a5d6ff',
|
|
221
|
+
warn: '#d29922',
|
|
222
|
+
error: '#f85149',
|
|
223
|
+
success: '#3fb950',
|
|
224
|
+
brown: '#db6d28',
|
|
225
|
+
toggleOn: '#56d4dd',
|
|
226
|
+
},
|
|
227
|
+
// Ayu Dark — the minimal warm-accent scheme from the Ayu editor theme.
|
|
228
|
+
ayu: {
|
|
229
|
+
bg: '#0f1419',
|
|
230
|
+
user: '#59c2ff',
|
|
231
|
+
userBar: '#35789e',
|
|
232
|
+
turnRule: '#1f2430',
|
|
233
|
+
turnLabel: '#5c6773',
|
|
234
|
+
prompt: '#ffb454',
|
|
235
|
+
dir: '#59c2ff',
|
|
236
|
+
assistant: '#c7ccd1',
|
|
237
|
+
tool: '#ff8f40',
|
|
238
|
+
toolResult: '#a0a8b0',
|
|
239
|
+
system: '#8a9199',
|
|
240
|
+
dim: '#5c6773',
|
|
241
|
+
taskSummary: '#39bae6',
|
|
242
|
+
warn: '#ffb454',
|
|
243
|
+
error: '#f07178',
|
|
244
|
+
success: '#aad94c',
|
|
245
|
+
brown: '#e6b450',
|
|
246
|
+
toggleOn: '#95e6cb',
|
|
247
|
+
},
|
|
248
|
+
// Rosé Pine — muted rose-pine tones, a calmer alternative to Dracula.
|
|
249
|
+
'rose-pine': {
|
|
250
|
+
bg: '#191724',
|
|
251
|
+
user: '#9ccfd8',
|
|
252
|
+
userBar: '#5b8490',
|
|
253
|
+
turnRule: '#26233a',
|
|
254
|
+
turnLabel: '#6e6a86',
|
|
255
|
+
prompt: '#f6c177',
|
|
256
|
+
dir: '#9ccfd8',
|
|
257
|
+
assistant: '#e0def4',
|
|
258
|
+
tool: '#ebbcba',
|
|
259
|
+
toolResult: '#908caa',
|
|
260
|
+
system: '#908caa',
|
|
261
|
+
dim: '#6e6a86',
|
|
262
|
+
taskSummary: '#c4a7e7',
|
|
263
|
+
warn: '#f6c177',
|
|
264
|
+
error: '#eb6f92',
|
|
265
|
+
success: '#31748f',
|
|
266
|
+
brown: '#ebbcba',
|
|
267
|
+
toggleOn: '#9ccfd8',
|
|
268
|
+
},
|
|
269
|
+
// Everforest — the soft green forest palette (low contrast, easy on eyes).
|
|
270
|
+
everforest: {
|
|
271
|
+
bg: '#2d353b',
|
|
272
|
+
user: '#7fbbb3',
|
|
273
|
+
userBar: '#5a8b85',
|
|
274
|
+
turnRule: '#3d484d',
|
|
275
|
+
turnLabel: '#859289',
|
|
276
|
+
prompt: '#dbbc7f',
|
|
277
|
+
dir: '#7fbbb3',
|
|
278
|
+
assistant: '#d3c6aa',
|
|
279
|
+
tool: '#e69875',
|
|
280
|
+
toolResult: '#9da9a0',
|
|
281
|
+
system: '#859289',
|
|
282
|
+
dim: '#7a8478',
|
|
283
|
+
taskSummary: '#a7c080',
|
|
284
|
+
warn: '#dbbc7f',
|
|
285
|
+
error: '#e67e80',
|
|
286
|
+
success: '#a7c080',
|
|
287
|
+
brown: '#e69875',
|
|
288
|
+
toggleOn: '#83c092',
|
|
289
|
+
},
|
|
290
|
+
// Kanagawa — Hokusai-inspired muted indigo (the "wave" theme).
|
|
291
|
+
kanagawa: {
|
|
292
|
+
bg: '#1f1f28',
|
|
293
|
+
user: '#7e9cd8',
|
|
294
|
+
userBar: '#55709e',
|
|
295
|
+
turnRule: '#2a2a37',
|
|
296
|
+
turnLabel: '#727169',
|
|
297
|
+
prompt: '#e6c384',
|
|
298
|
+
dir: '#7fb4ca',
|
|
299
|
+
assistant: '#dcd7ba',
|
|
300
|
+
tool: '#ffa066',
|
|
301
|
+
toolResult: '#a6a69c',
|
|
302
|
+
system: '#c8c093',
|
|
303
|
+
dim: '#727169',
|
|
304
|
+
taskSummary: '#957fb8',
|
|
305
|
+
warn: '#e6c384',
|
|
306
|
+
error: '#e46876',
|
|
307
|
+
success: '#98bb6c',
|
|
308
|
+
brown: '#d27e59',
|
|
309
|
+
toggleOn: '#7aa89f',
|
|
310
|
+
},
|
|
182
311
|
};
|
|
183
312
|
/** The palette id used when the configured theme is unknown/missing. */
|
|
184
313
|
export const DEFAULT_THEME = 'zames';
|
|
@@ -239,6 +368,9 @@ export function applyTheme(name) {
|
|
|
239
368
|
theme[key] = built[key];
|
|
240
369
|
}
|
|
241
370
|
activeName = next;
|
|
371
|
+
// The window background is part of the look: paint it (best effort) so a
|
|
372
|
+
// dark palette does not sit on a light terminal background.
|
|
373
|
+
applyBackground(next);
|
|
242
374
|
return next;
|
|
243
375
|
}
|
|
244
376
|
/**
|
package/dist/tui/ConfigMenu.js
CHANGED
|
@@ -14,6 +14,7 @@ import { useMemo, useState } from 'react';
|
|
|
14
14
|
import { Box, Text, useInput, useApp, useWindowSize } from 'ink';
|
|
15
15
|
import { translate } from '../i18n.js';
|
|
16
16
|
import { Checkbox, Select, UI, HintBar } from './widgets.js';
|
|
17
|
+
import { ThemePreview } from './theme-preview.js';
|
|
17
18
|
/** Strip ANSI to measure a line's visible width. */
|
|
18
19
|
function stripAnsi(s) {
|
|
19
20
|
return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
@@ -52,6 +53,8 @@ export function ConfigMenuApp(opts) {
|
|
|
52
53
|
const [editBuf, setEditBuf] = useState('');
|
|
53
54
|
const [pendingReset, setPendingReset] = useState(null);
|
|
54
55
|
const [message, setMessage] = useState(null);
|
|
56
|
+
// Inline palette preview of the theme row ('p').
|
|
57
|
+
const [previewTheme, setPreviewTheme] = useState(false);
|
|
55
58
|
// `get`/`set` mutate the LIVE config object in place, so React has no state
|
|
56
59
|
// change to react to and the row kept the OLD value on screen after a
|
|
57
60
|
// toggle/cycle (the operator saw «ru» while the config already held «en»).
|
|
@@ -216,12 +219,14 @@ export function ConfigMenuApp(opts) {
|
|
|
216
219
|
setCursor((c) => visible.length ? (c - 1 + visible.length) % visible.length : 0);
|
|
217
220
|
setPendingReset(null);
|
|
218
221
|
setMessage(null);
|
|
222
|
+
setPreviewTheme(false);
|
|
219
223
|
return;
|
|
220
224
|
}
|
|
221
225
|
if (key.downArrow || input === 'j') {
|
|
222
226
|
setCursor((c) => (visible.length ? (c + 1) % visible.length : 0));
|
|
223
227
|
setPendingReset(null);
|
|
224
228
|
setMessage(null);
|
|
229
|
+
setPreviewTheme(false);
|
|
225
230
|
return;
|
|
226
231
|
}
|
|
227
232
|
if (input === 'g') {
|
|
@@ -238,6 +243,30 @@ export function ConfigMenuApp(opts) {
|
|
|
238
243
|
setMessage(null);
|
|
239
244
|
return;
|
|
240
245
|
}
|
|
246
|
+
if (input === 'p' || input === 'P') {
|
|
247
|
+
// Inline palette PREVIEW for the theme row: the settings list is where
|
|
248
|
+
// most operators meet ui.theme, and cycling the enum only shows the id.
|
|
249
|
+
// 'p' opens the sample session painted with the CURRENT palette, so the
|
|
250
|
+
// choice can be judged without leaving /config. Other rows: no-op.
|
|
251
|
+
const f = visible[cursor];
|
|
252
|
+
if (f && f.path === 'ui.theme') {
|
|
253
|
+
setPreviewTheme((v) => !v);
|
|
254
|
+
setMessage(null);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (input === 'p' || input === 'P') {
|
|
259
|
+
// Inline palette PREVIEW on the theme row: /config is where most
|
|
260
|
+
// operators meet ui.theme, and cycling the enum only shows the id. 'p'
|
|
261
|
+
// toggles a sample session painted with the CURRENT palette, so the
|
|
262
|
+
// choice can be judged without leaving the menu. Other rows: no-op.
|
|
263
|
+
const f = visible[cursor];
|
|
264
|
+
if (f && f.path === 'ui.theme') {
|
|
265
|
+
setPreviewTheme((v) => !v);
|
|
266
|
+
setMessage(null);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
241
270
|
if (key.return || input === ' ') {
|
|
242
271
|
activate();
|
|
243
272
|
return;
|
|
@@ -336,10 +365,13 @@ export function ConfigMenuApp(opts) {
|
|
|
336
365
|
if (!visible.length) {
|
|
337
366
|
items.push(_jsx(Text, { color: UI.dim, children: ' ' + t('cfg.menu.no_match') }, "none"));
|
|
338
367
|
}
|
|
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: [
|
|
368
|
+
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)) }), previewTheme && visible[cursor]?.path === 'ui.theme' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.dim, children: t('cfg.menu.theme_preview') }), _jsx(ThemePreview, { name: String(get('ui.theme') ?? 'zames'), compact: true })] })) : null, 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: [
|
|
340
369
|
['↑/↓', hint('tui.hint.select')],
|
|
341
370
|
['←/→', hint('tui.hint.change')],
|
|
342
371
|
['Enter', hint('tui.hint.edit')],
|
|
372
|
+
...(visible[cursor]?.path === 'ui.theme'
|
|
373
|
+
? [['p', hint('cfg.menu.theme_preview_key')]]
|
|
374
|
+
: []),
|
|
343
375
|
['/', hint('tui.hint.search')],
|
|
344
376
|
['d', hint('tui.hint.reset')],
|
|
345
377
|
['q', hint('tui.hint.quit')],
|
package/dist/tui/InputLine.js
CHANGED
|
@@ -12,7 +12,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
12
12
|
import { Box, Text, useInput, usePaste, useCursor, useWindowSize, useStdin, } from 'ink';
|
|
13
13
|
import { translate } from '../i18n.js';
|
|
14
14
|
import { expandPastes, inputInnerCols, layoutInput, SUGGEST_PAGE, } from '../input/layout.js';
|
|
15
|
-
import { insertText, insertPaste, insertNewline, backspace, del, moveLeft, moveRight, moveHome, moveEnd, wordLeft, wordRight, deleteWordLeft, deleteWordRight, killToLineEnd, moveVisualRows, pushUndo, undoEdit, historyUp, historyDown, searchStart, searchNext, searchAccept, searchCancel, searchInput, searchBackspace, recordHistory, afterSubmit, } from './edit.js';
|
|
15
|
+
import { insertText, insertPaste, insertNewline, backspace, del, moveLeft, moveRight, moveHome, moveEnd, wordLeft, wordRight, deleteWordLeft, deleteWordRight, killToLineEnd, moveVisualRows, pushUndo, undoEdit, historyUp, historyDown, searchStart, searchNext, searchAccept, searchCancel, searchInput, searchBackspace, recordHistory, afterSubmit, rememberDraft, } from './edit.js';
|
|
16
16
|
import { dispatchKey, parseCsiU } from './keys.js';
|
|
17
17
|
import { useSyncExternalStore } from 'react';
|
|
18
18
|
import { matchSlashCommands, suggestWindow, completeSlashCommand, } from '../input/suggest.js';
|
|
@@ -125,17 +125,20 @@ export function InputLine(props) {
|
|
|
125
125
|
case 'submit':
|
|
126
126
|
doSubmit();
|
|
127
127
|
return;
|
|
128
|
+
// The EDIT actions first write the line into the draft slot, so a
|
|
129
|
+
// remembered draft is replaced only by a REAL edit and an accidental
|
|
130
|
+
// Up cannot lose the typed message.
|
|
128
131
|
case 'newline':
|
|
129
|
-
apply(insertNewline);
|
|
132
|
+
apply((s) => pushUndo(rememberDraft(insertNewline(s)), true));
|
|
130
133
|
return;
|
|
131
134
|
case 'insert':
|
|
132
|
-
apply((s) => pushUndo(insertText(s, action.text), true));
|
|
135
|
+
apply((s) => pushUndo(rememberDraft(insertText(s, action.text)), true));
|
|
133
136
|
return;
|
|
134
137
|
case 'backspace':
|
|
135
|
-
apply((s) => backspace(pushUndo(s, true)));
|
|
138
|
+
apply((s) => backspace(pushUndo(rememberDraft(s), true)));
|
|
136
139
|
return;
|
|
137
140
|
case 'delete':
|
|
138
|
-
apply((s) => del(pushUndo(s, true)));
|
|
141
|
+
apply((s) => del(pushUndo(rememberDraft(s), true)));
|
|
139
142
|
return;
|
|
140
143
|
case 'left':
|
|
141
144
|
apply(moveLeft);
|
package/dist/tui/ThemePicker.js
CHANGED
|
@@ -10,7 +10,7 @@ import { useState } from 'react';
|
|
|
10
10
|
import { Box, Text, useInput, useApp, useWindowSize } from 'ink';
|
|
11
11
|
import { UI } from './ui-colors.js';
|
|
12
12
|
import { HintBar } from './widgets.js';
|
|
13
|
-
import {
|
|
13
|
+
import { ThemePreview } from './theme-preview.js';
|
|
14
14
|
import { translate } from '../i18n.js';
|
|
15
15
|
export function ThemePicker({ themes, initial, onPreview, locale = 'en', onDone, }) {
|
|
16
16
|
const { exit } = useApp();
|
|
@@ -30,20 +30,6 @@ export function ThemePicker({ themes, initial, onPreview, locale = 'en', onDone,
|
|
|
30
30
|
setCursor(next);
|
|
31
31
|
onPreview(themes[next]);
|
|
32
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
33
|
useInput((input, key) => {
|
|
48
34
|
if (key.escape || (key.ctrl && input === 'c') || input === 'q') {
|
|
49
35
|
finish(null);
|
|
@@ -63,7 +49,7 @@ export function ThemePicker({ themes, initial, onPreview, locale = 'en', onDone,
|
|
|
63
49
|
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
50
|
const sel = idx === cursor;
|
|
65
51
|
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(
|
|
52
|
+
}), _jsx(Text, { color: UI.border, children: '─'.repeat(innerCols) }), _jsx(ThemePreview, { name: themes[cursor] ?? initial, compact: true }), _jsx(HintBar, { hints: [
|
|
67
53
|
['↑/↓', t('theme.pick_move')],
|
|
68
54
|
['Enter', t('tui.hint.ok')],
|
|
69
55
|
['Esc', t('theme.pick_cancel')],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Terminal BACKGROUND handling.
|
|
2
|
+
//
|
|
3
|
+
// A terminal cannot be told to paint "the background" — only the cells an app
|
|
4
|
+
// writes are styled. So a theme's `bg` reaches the screen by
|
|
5
|
+
// 1. emitting an OSC-11 background colour for the whole window (most modern
|
|
6
|
+
// terminals support it; a terminal that does not answers nothing), and
|
|
7
|
+
// 2. re-emitting it AFTER a full-screen app exits: the alternate screen keeps
|
|
8
|
+
// the window's background, and Ink's exit sequence resets attributes
|
|
9
|
+
// (SGR reset does not touch OSC-11, but some terminals do reset the colour
|
|
10
|
+
// on 1049l). Re-emitting is harmless where unsupported.
|
|
11
|
+
//
|
|
12
|
+
// The OSC sequence is written STRAIGHT to stdout (not through chalk), because
|
|
13
|
+
// it is not an SGR attribute: `\x1b]11;rgb:rr/gg/bb\x07`.
|
|
14
|
+
import { PALETTES, isThemeName, DEFAULT_THEME } from '../theme.js';
|
|
15
|
+
/** `#rrggbb` -> the `rr/gg/bb` part of an OSC-11 payload. */
|
|
16
|
+
function hexToOsc(hex) {
|
|
17
|
+
const h = hex.replace('#', '');
|
|
18
|
+
return `${h.slice(0, 2)}/${h.slice(2, 4)}/${h.slice(4, 6)}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The OSC-11 sequence that sets the window background, or '' when the operator
|
|
22
|
+
* opted out (NO_COLOR / ZAMES_NO_BG). Exported pure, so the encoding is
|
|
23
|
+
* testable without a terminal.
|
|
24
|
+
*/
|
|
25
|
+
export function backgroundOsc(name) {
|
|
26
|
+
if (process.env.NO_COLOR || process.env.ZAMES_NO_BG)
|
|
27
|
+
return '';
|
|
28
|
+
const id = isThemeName(name) ? name : DEFAULT_THEME;
|
|
29
|
+
return '\x1b]11;rgb:' + hexToOsc(PALETTES[id].bg) + '\x07';
|
|
30
|
+
}
|
|
31
|
+
/** Paint the window background for the palette `name` (best effort). */
|
|
32
|
+
export function applyBackground(name) {
|
|
33
|
+
const seq = backgroundOsc(name);
|
|
34
|
+
if (!seq)
|
|
35
|
+
return;
|
|
36
|
+
try {
|
|
37
|
+
process.stdout.write(seq);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// A closed/redirected stdout must not break a theme switch.
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/tui/edit.js
CHANGED
|
@@ -249,9 +249,11 @@ export function historyUp(s) {
|
|
|
249
249
|
if (!s.history.length)
|
|
250
250
|
return s;
|
|
251
251
|
let histIndex = s.histIndex;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
// Snapshot the unsent line when the browse STARTS (histIndex is at the end),
|
|
253
|
+
// but NEVER replace the remembered draft with a RECALLED entry's text: a
|
|
254
|
+
// second Up after a Down used to overwrite it, and the operator's typed
|
|
255
|
+
// message was lost ("pressed Up by accident").
|
|
256
|
+
const histDraft = histIndex === s.history.length ? s.buf : s.histDraft;
|
|
255
257
|
if (histIndex > 0) {
|
|
256
258
|
histIndex--;
|
|
257
259
|
const buf = s.history[histIndex];
|
|
@@ -269,6 +271,10 @@ export function historyDown(s) {
|
|
|
269
271
|
if (!s.history.length)
|
|
270
272
|
return s;
|
|
271
273
|
let histIndex = s.histIndex;
|
|
274
|
+
// The CURRENT line is the operator's, not a history entry: leaving the
|
|
275
|
+
// history must bring it back. Without this, editing a recalled line and then
|
|
276
|
+
// pressing Down restored the OLD draft and dropped the edit.
|
|
277
|
+
const draft = browsingHistory(s) ? s.histDraft : s.buf;
|
|
272
278
|
let buf;
|
|
273
279
|
if (histIndex < s.history.length - 1) {
|
|
274
280
|
histIndex++;
|
|
@@ -276,9 +282,38 @@ export function historyDown(s) {
|
|
|
276
282
|
}
|
|
277
283
|
else {
|
|
278
284
|
histIndex = s.history.length;
|
|
279
|
-
buf =
|
|
285
|
+
buf = draft;
|
|
280
286
|
}
|
|
281
|
-
return {
|
|
287
|
+
return {
|
|
288
|
+
...s,
|
|
289
|
+
histIndex,
|
|
290
|
+
histDraft: draft,
|
|
291
|
+
buf,
|
|
292
|
+
cursor: Array.from(buf).length,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// ---------- draft guard ----------
|
|
296
|
+
//
|
|
297
|
+
// The operator's UNSUBMITTED line must survive a history browse, arrow-key
|
|
298
|
+
// scrolling and every other non-typing transition. `histDraft` alone did not:
|
|
299
|
+
// it is snapshotted only when the browse STARTS (histIndex === history.length),
|
|
300
|
+
// so once the cursor was inside the history an edit or a second Up path could
|
|
301
|
+
// replace it — a real report was "I pressed Up by accident and my typed message
|
|
302
|
+
// was gone".
|
|
303
|
+
//
|
|
304
|
+
// The rule: typing/recalling may change the visible buffer, but a transition
|
|
305
|
+
// that is NOT an explicit edit must keep the draft; leaving the history restores
|
|
306
|
+
// it. `typingRun` is true while the operator types (so a real edit adopts the new
|
|
307
|
+
// text as the draft on the next browse).
|
|
308
|
+
/** True while the buffer is a RECALLED history entry (not the draft). */
|
|
309
|
+
export function browsingHistory(s) {
|
|
310
|
+
return s.history.length > 0 && s.histIndex < s.history.length;
|
|
311
|
+
}
|
|
312
|
+
/** Remember the current buffer as the draft when it is NOT a history entry. */
|
|
313
|
+
export function rememberDraft(s) {
|
|
314
|
+
if (s.buf === s.histDraft)
|
|
315
|
+
return s;
|
|
316
|
+
return { ...s, histDraft: s.buf };
|
|
282
317
|
}
|
|
283
318
|
export function searchStart(s) {
|
|
284
319
|
if (!s.history.length)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { PALETTES, DEFAULT_THEME, isThemeName } from '../theme.js';
|
|
5
|
+
/**
|
|
6
|
+
* The sample lines. Kept PURE DATA so tests can assert the preview covers the
|
|
7
|
+
* important roles and the Ink/plain renderers cannot drift apart.
|
|
8
|
+
*/
|
|
9
|
+
export function previewLines() {
|
|
10
|
+
return [
|
|
11
|
+
{ role: 'dir', text: '~/project' },
|
|
12
|
+
{ role: 'prompt', text: '❯ объясни этот код', bold: true },
|
|
13
|
+
{ role: 'assistant', text: '● Ответ: функция читает файл и парсит JSON.' },
|
|
14
|
+
{ role: 'tool', text: '🔧 Read {"path":"package.json"}' },
|
|
15
|
+
{ role: 'toolResult', text: '→ ✔ 42 строки' },
|
|
16
|
+
{ role: 'taskSummary', text: '· duration: 6s · tools: 1' },
|
|
17
|
+
{ role: 'warn', text: '▲ предупреждение' },
|
|
18
|
+
{ role: 'error', text: '✖ ошибка' },
|
|
19
|
+
{ role: 'success', text: '✔ готово' },
|
|
20
|
+
{ role: 'toggleOn', text: '🌐 поиск вкл' },
|
|
21
|
+
{ role: 'system', text: '● сервисное сообщение' },
|
|
22
|
+
{ role: 'dim', text: '· подсказка' },
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
function paletteOf(name) {
|
|
26
|
+
return PALETTES[isThemeName(name) ? name : DEFAULT_THEME];
|
|
27
|
+
}
|
|
28
|
+
/** The preview as one chalk-painted string (for a plain terminal line). */
|
|
29
|
+
export function themePreviewAnsi(name) {
|
|
30
|
+
const p = paletteOf(name);
|
|
31
|
+
return previewLines()
|
|
32
|
+
.map((l) => {
|
|
33
|
+
const c = chalk.hex(p[l.role]);
|
|
34
|
+
return l.bold ? c.bold(l.text) : c(l.text);
|
|
35
|
+
})
|
|
36
|
+
.join('\n');
|
|
37
|
+
}
|
|
38
|
+
/** The preview as an Ink block (for the picker / the config menu). */
|
|
39
|
+
export function ThemePreview({ name, compact = false, }) {
|
|
40
|
+
const p = paletteOf(name);
|
|
41
|
+
const lines = compact
|
|
42
|
+
? previewLines().filter((l) => ['prompt', 'assistant', 'tool', 'error', 'success', 'dim'].includes(l.role))
|
|
43
|
+
: previewLines();
|
|
44
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((l, i) => (_jsx(Text, { color: p[l.role], bold: l.bold, children: l.text }, i))) }));
|
|
45
|
+
}
|
package/llms-full.txt
CHANGED
|
@@ -23,7 +23,7 @@ lock-in. Install with `npm install -g zames_pro`, run `zames` in any project
|
|
|
23
23
|
folder, and describe the task in plain language.
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
|
-
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/
|
|
26
|
+
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/shots/theme-zames.png" alt="zames terminal coding agent session — an AI coding agent working in the command line without an API key" width="860">
|
|
27
27
|
</p>
|
|
28
28
|
|
|
29
29
|
## Quick start
|
|
@@ -616,10 +616,19 @@ The switch is instant, no restart. `zames` is the default; the bundled palettes
|
|
|
616
616
|
are ports of well-known editor schemes:
|
|
617
617
|
|
|
618
618
|
`zames` (default), `one-dark`, `dracula`, `gruvbox`, `nord`, `monokai`,
|
|
619
|
-
`solarized-dark`, `tokyo-night
|
|
619
|
+
`solarized-dark`, `tokyo-night`, `catppuccin`, `github-dark`, `ayu`,
|
|
620
|
+
`rose-pine`, `everforest`, `kanagawa`.
|
|
620
621
|
|
|
621
622
|
A theme repaints EVERYTHING: the interface, the agent's answers, the code
|
|
622
|
-
highlighting inside them and the input frame. The
|
|
623
|
+
highlighting inside them, the status row and the input frame. The terminal's
|
|
624
|
+
BACKGROUND follows too: the theme emits OSC-11 with the palette's `bg`
|
|
625
|
+
(supported by iTerm2, Windows Terminal, kitty, WezTerm, Alacritty, GNOME
|
|
626
|
+
Terminal and others; where it is not supported nothing happens). Opt out with
|
|
627
|
+
`ZAMES_NO_BG=1` or `NO_COLOR`.
|
|
628
|
+
To make the choice
|
|
629
|
+
concrete, a SAMPLE SESSION in that palette is shown before you pick (in `/theme`
|
|
630
|
+
below the list, in `/config` press `p` on the theme row); after
|
|
631
|
+
`/theme <id>` the sample is printed too. The frame itself is a separate
|
|
623
632
|
setting: `/config set ui.inputBorder <round|single|double|bold|none>`
|
|
624
633
|
(default `round`; `none` removes it).
|
|
625
634
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.85.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": {
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"postinstall": "node scripts/postinstall.mjs",
|
|
48
48
|
"prepare": "husky",
|
|
49
49
|
"glm-smoke": "tsx scripts/glm-smoke.mts",
|
|
50
|
-
"seo:topics": "tsx scripts/seo-topics.mts"
|
|
50
|
+
"seo:topics": "tsx scripts/seo-topics.mts",
|
|
51
|
+
"ui-smoke": "tsx scripts/ui-smoke.mts"
|
|
51
52
|
},
|
|
52
53
|
"keywords": [
|
|
53
54
|
"zames",
|