zames_pro 2.74.0 → 2.75.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 +38 -0
- package/README.md +24 -0
- package/dist/attachments.js +6 -1
- package/dist/commands.js +0 -1
- package/dist/config.js +7 -7
- package/dist/i18n/common.js +0 -8
- package/dist/i18n/config.js +0 -1
- package/dist/i18n/help.js +8 -4
- package/dist/i18n/qwen.js +0 -4
- package/dist/i18n/self-review.js +2 -18
- package/dist/i18n/spinner.js +5 -0
- package/dist/index.js +30 -43
- package/dist/input/suggest.js +19 -4
- package/dist/theme.js +9 -0
- package/dist/tui/ConfigMenu.js +44 -11
- package/dist/tui/FullScreenApp.js +44 -11
- package/dist/tui/InputLine.js +52 -19
- package/dist/tui/SelectList.js +6 -2
- package/dist/tui/StatusBar.js +66 -36
- package/dist/tui/TuiApp.js +3 -2
- package/dist/tui/TuiEditor.js +44 -4
- package/dist/tui/edit.js +21 -1
- package/dist/tui/keys.js +46 -2
- package/dist/tui/store.js +1 -1
- package/dist/tui/useMouseWheel.js +80 -29
- package/dist/tui/widgets.js +6 -1
- package/package.json +2 -1
- package/scripts/ctd-live-driver.mts +51 -0
- package/scripts/ctd-live.tsx +33 -0
- package/scripts/escd-live-driver.mts +37 -0
- package/scripts/inline-status-driver.mts +31 -0
- package/scripts/inline-status.tsx +33 -0
- package/scripts/mouse-churn.mts +34 -0
- package/scripts/qwen-models-smoke.mts +117 -0
- package/scripts/ru-shot.mts +20 -0
- package/scripts/tui-comp-shot.mts +63 -0
- package/scripts/tui-components.tsx +62 -0
- package/scripts/tui-config-driver.mts +82 -0
- package/scripts/tui-config-probe.tsx +74 -0
- package/scripts/tui-edit-driver.mts +100 -0
- package/scripts/tui-edit-probe.tsx +38 -0
- package/scripts/tui-editor-probe.tsx +24 -0
- package/scripts/tui-input-driver.mts +54 -0
- package/scripts/tui-input-shot.tsx +59 -0
- package/scripts/tui-log-driver.mts +35 -0
- package/scripts/tui-log-shot.tsx +48 -0
- package/scripts/tui-longpaste-driver.mts +56 -0
- package/scripts/tui-longpaste.tsx +35 -0
- package/scripts/tui-manyselect-shot.mts +34 -0
- package/scripts/tui-manyselect.tsx +17 -0
- package/scripts/tui-mouse-churn-probe.tsx +28 -0
- package/scripts/tui-paste-driver.mts +88 -0
- package/scripts/tui-paste-kinds.tsx +76 -0
- package/scripts/tui-pastetest.mts +38 -0
- package/scripts/tui-repro.tsx +64 -0
- package/scripts/tui-resize.mts +70 -0
- package/scripts/tui-scroll-driver.mts +53 -0
- package/scripts/tui-scroll-probe.tsx +46 -0
- package/scripts/tui-shot.mts +106 -0
- package/scripts/tui-status-driver.mts +24 -0
- package/scripts/tui-status-shot.tsx +27 -0
- package/scripts/tui-submit.tsx +57 -0
- package/scripts/tui-wheel-driver.mts +61 -0
- package/scripts/tui-wheel-probe.tsx +48 -0
- package/scripts/tui-widgets-driver.mts +20 -0
- package/scripts/tui-widgets.tsx +44 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.75.1] - 2026-10-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- remove /resume; Ctrl+Delete always deletes the word BEFORE the cursor
|
|
15
|
+
- **tui:** drop the working directory from the prompt
|
|
16
|
+
- **tui:** persistent key hint under the input box
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- stop advertising the removed /resume and make the i18n guard real
|
|
21
|
+
- **tui:** Ctrl+Delete works in VSCode (it sends ESC d)
|
|
22
|
+
- **tui:** Ctrl+Delete deletes the word BEFORE the cursor
|
|
23
|
+
|
|
24
|
+
## [2.75.0] - 2026-10-14
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **tui:** show a one-time hint about mouse selection
|
|
29
|
+
- **tui:** Home/End jump to the top/bottom of the dialog
|
|
30
|
+
- **tui:** mouse wheel scrolls the dialog by default
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **tui:** the status row keeps its height (inline caret on the input row)
|
|
35
|
+
- **tui:** the status bar never wraps to a second line
|
|
36
|
+
- **tui:** the caret lands correctly in a CJK buffer
|
|
37
|
+
- **tui:** responsive dividers and wrapping-aware menu window
|
|
38
|
+
- **tui:** log scroll reads the LIVE offset (no stale closure)
|
|
39
|
+
- **tui:** Ctrl+Delete works (kitty CSI-u) and deletes left at end of line
|
|
40
|
+
- **tui:** the mouse wheel no longer scrolls the input history (for good)
|
|
41
|
+
- **attachments:** keep the items array identity across reset()
|
|
42
|
+
- **tui:** the config menu now redraws on change and fits the terminal
|
|
43
|
+
- **tui:** reserve the real number of rows for a wrapping suggest popup
|
|
44
|
+
- **tui:** exact badge width (T0.7) and honor NO_COLOR (T0.11)
|
|
45
|
+
- **tui:** deliver the FULL pasted text to the agent, not the [Pasted lines#N] marker
|
|
46
|
+
- **tui:** separate SELECT/SCROLL mouse modes and stop the header wrapping the panel
|
|
47
|
+
|
|
10
48
|
## [2.74.0] - 2026-10-13
|
|
11
49
|
|
|
12
50
|
### Added
|
package/README.md
CHANGED
|
@@ -255,6 +255,18 @@ The prompt is a small line editor with persistent history:
|
|
|
255
255
|
Claude Code's bash mode). The same sandbox guard as the `Bash` tool applies,
|
|
256
256
|
so a direct command cannot leave the project either.
|
|
257
257
|
|
|
258
|
+
Full-screen mode adds dialog navigation, so a long answer can be read and
|
|
259
|
+
selected without leaving the terminal:
|
|
260
|
+
|
|
261
|
+
- mouse wheel — scroll the dialog (never the input history; history is arrow
|
|
262
|
+
keys only). Selecting text needs `Shift`+drag, the standard TUI convention.
|
|
263
|
+
- `PgUp` / `PgDn` — scroll the dialog half a screen.
|
|
264
|
+
- `Home` / `End` — jump to the very top / bottom of the dialog (only while the
|
|
265
|
+
input line is empty; with text typed they move the caret as usual).
|
|
266
|
+
- To copy a LARGE block, use `/export` (writes the whole session as Markdown)
|
|
267
|
+
or the transcript file (`/transcript` shows the path) — the terminal's own
|
|
268
|
+
drag-select cannot reach past one screen.
|
|
269
|
+
|
|
258
270
|
Set `NO_COLOR=1` to disable colors (a calm default palette is used otherwise).
|
|
259
271
|
|
|
260
272
|
### Images and files
|
|
@@ -823,6 +835,18 @@ zames
|
|
|
823
835
|
Claude Code). Действует та же песочница, что и для инструмента `Bash`, так
|
|
824
836
|
что прямая команда тоже не может выйти за пределы проекта.
|
|
825
837
|
|
|
838
|
+
В полноэкранном режиме добавляется навигация по диалогу, чтобы длинный ответ
|
|
839
|
+
можно было прочитать и выделить не покидая терминал:
|
|
840
|
+
|
|
841
|
+
- колесо мыши — скролл диалога (историю ввода колесо НЕ листает; история —
|
|
842
|
+
только стрелки). Выделение текста — `Shift`+протяжка, стандарт TUI.
|
|
843
|
+
- `PgUp` / `PgDn` — листать диалог на пол-экрана.
|
|
844
|
+
- `Home` / `End` — прыжок в самый верх / низ диалога (только когда строка ввода
|
|
845
|
+
пуста; при набранном тексте они двигают курсор, как обычно).
|
|
846
|
+
- Чтобы скопировать БОЛЬШОЙ кусок, используйте `/export` (пишет всю сессию в
|
|
847
|
+
Markdown) или файл транскрипта (`/transcript` покажет путь) — собственным
|
|
848
|
+
выделением терминала за пределы одного экрана не выделить.
|
|
849
|
+
|
|
826
850
|
Установите `NO_COLOR=1`, чтобы отключить цвета (иначе используется спокойная
|
|
827
851
|
палитра по умолчанию).
|
|
828
852
|
|
package/dist/attachments.js
CHANGED
|
@@ -141,7 +141,12 @@ export class AttachmentStore {
|
|
|
141
141
|
imageCount = 0;
|
|
142
142
|
fileCount = 0;
|
|
143
143
|
reset() {
|
|
144
|
-
this.items
|
|
144
|
+
// Empty IN PLACE: the Ink tree captures `this.items` by reference at mount
|
|
145
|
+
// (InputLine's `attachments` prop). Reassigning to a new array left the
|
|
146
|
+
// tree holding the OLD one, so after the first submit every new attachment
|
|
147
|
+
// was appended to an array the UI no longer read — `onSubmit` got `[]` and
|
|
148
|
+
// the pasted image/file was silently dropped.
|
|
149
|
+
this.items.length = 0;
|
|
145
150
|
this.imageCount = 0;
|
|
146
151
|
this.fileCount = 0;
|
|
147
152
|
}
|
package/dist/commands.js
CHANGED
|
@@ -981,7 +981,6 @@ export const SLASH_COMMANDS = [
|
|
|
981
981
|
{ name: '/clear', key: 'help.cmd.new' },
|
|
982
982
|
{ name: '/sessions', key: 'help.cmd.sessions' },
|
|
983
983
|
{ name: '/chats', key: 'help.cmd.chats' },
|
|
984
|
-
{ name: '/resume', key: 'help.cmd.resume' },
|
|
985
984
|
{ name: '/resume-id', key: 'help.cmd.resume_id' },
|
|
986
985
|
{ name: '/last', key: 'help.cmd.last' },
|
|
987
986
|
{ name: '/retry', key: 'help.cmd.retry' },
|
package/dist/config.js
CHANGED
|
@@ -101,13 +101,13 @@ export const DEFAULTS = {
|
|
|
101
101
|
// layout, which keeps the scrollback but is subject to reflow artifacts.
|
|
102
102
|
alternateScreen: true,
|
|
103
103
|
// In-app MOUSE-WHEEL scrolling of the log. ON by default: the operator
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
104
|
+
// asked that the wheel scroll the DIALOG (and it must NEVER scroll the
|
|
105
|
+
// input history — history is arrow keys only). That needs terminal mouse
|
|
106
|
+
// REPORTING (1000/1006) plus alternate-scroll OFF (1007l), so the wheel
|
|
107
|
+
// arrives as SGR events we consume and never as Up/Down arrows. The
|
|
108
|
+
// trade-off is that the terminal no longer does its own drag-select, so
|
|
109
|
+
// selecting text needs SHIFT+DRAG (the standard TUI convention). Set to
|
|
110
|
+
// false to give the terminal the mouse back (native select, wheel inert).
|
|
111
111
|
mouseScroll: true,
|
|
112
112
|
},
|
|
113
113
|
// Off by default: a full tsc/eslint run after every edit is slow; opt in.
|
package/dist/i18n/common.js
CHANGED
|
@@ -254,14 +254,6 @@ export const commonMessages = {
|
|
|
254
254
|
ru: 'Нет блока кода #{n}.',
|
|
255
255
|
en: 'No code block #{n}.',
|
|
256
256
|
},
|
|
257
|
-
'copy.code_hint': {
|
|
258
|
-
ru: 'блок кода {n}/{m} · Ctrl+Y — копировать',
|
|
259
|
-
en: 'code block {n}/{m} · Ctrl+Y to copy',
|
|
260
|
-
},
|
|
261
|
-
'copy_code.usage': {
|
|
262
|
-
ru: 'Использование: /copy-code [номер] — копировать блок кода последнего ответа (по умолчанию последний). /copy-code list — список.',
|
|
263
|
-
en: 'Usage: /copy-code [n] — copy a code block from the last answer (default: the last). /copy-code list — list them.',
|
|
264
|
-
},
|
|
265
257
|
'copy_code.list': {
|
|
266
258
|
ru: 'Блоки кода последнего ответа:',
|
|
267
259
|
en: 'Code blocks in the last answer:',
|
package/dist/i18n/config.js
CHANGED
|
@@ -6,7 +6,6 @@ export const configMessages = {
|
|
|
6
6
|
'cfg.group.checkpoint': { ru: 'Чекпойнты', en: 'Checkpoints' },
|
|
7
7
|
'cfg.group.transcript': { ru: 'Транскрипт', en: 'Transcript' },
|
|
8
8
|
'cfg.group.browser': { ru: 'Браузер / DeepSeek', en: 'Browser / DeepSeek' },
|
|
9
|
-
'cfg.group.qwen': { ru: 'Qwen', en: 'Qwen' },
|
|
10
9
|
'cfg.f.provider': { ru: 'Провайдер чата', en: 'Chat provider' },
|
|
11
10
|
'cfg.f.qwen_model': {
|
|
12
11
|
ru: 'Модель Qwen (id)',
|
package/dist/i18n/help.js
CHANGED
|
@@ -97,6 +97,14 @@ export const helpMessages = {
|
|
|
97
97
|
ru: 'Esc, Ctrl+C прервать текущую генерацию',
|
|
98
98
|
en: 'Esc, Ctrl+C abort current generation',
|
|
99
99
|
},
|
|
100
|
+
'help.key.scroll': {
|
|
101
|
+
ru: 'PgUp / PgDn листать диалог (Home/End — в начало/конец) [full-screen]',
|
|
102
|
+
en: 'PgUp / PgDn scroll the dialog (Home/End — top/bottom) [full-screen]',
|
|
103
|
+
},
|
|
104
|
+
'help.key.wheel': {
|
|
105
|
+
ru: 'колесо мыши скролл диалога; выделение — Shift+протяжка',
|
|
106
|
+
en: 'mouse wheel scroll the dialog; select text with Shift+drag',
|
|
107
|
+
},
|
|
100
108
|
'help.key.bang': {
|
|
101
109
|
ru: '!команда выполнить shell-команду напрямую (мимо модели)',
|
|
102
110
|
en: '!command run a shell command directly (bypass the model)',
|
|
@@ -125,10 +133,6 @@ export const helpMessages = {
|
|
|
125
133
|
ru: '/chats [фильтр] список последних чатов DeepSeek',
|
|
126
134
|
en: '/chats [filter] recent DeepSeek chats',
|
|
127
135
|
},
|
|
128
|
-
'help.cmd.resume': {
|
|
129
|
-
ru: '/resume <n> открыть чат №n из /chats',
|
|
130
|
-
en: '/resume <n> open chat #n from /chats',
|
|
131
|
-
},
|
|
132
136
|
'help.cmd.last': {
|
|
133
137
|
ru: '/last открыть последний чат этого каталога',
|
|
134
138
|
en: '/last open the last chat of this directory',
|
package/dist/i18n/qwen.js
CHANGED
|
@@ -14,10 +14,6 @@ export const qwenMessages = {
|
|
|
14
14
|
ru: 'Код из письма Qwen: ',
|
|
15
15
|
en: 'Qwen email code: ',
|
|
16
16
|
},
|
|
17
|
-
'qwen.login_pass_prompt': {
|
|
18
|
-
ru: 'Пароль Qwen: ',
|
|
19
|
-
en: 'Qwen password: ',
|
|
20
|
-
},
|
|
21
17
|
'qwen.login_ok': {
|
|
22
18
|
ru: 'Qwen: вход выполнен.',
|
|
23
19
|
en: 'Qwen: signed in.',
|
package/dist/i18n/self-review.js
CHANGED
|
@@ -113,8 +113,8 @@ export const selfReviewMessages = {
|
|
|
113
113
|
en: 'Pick a chat (↑/↓, Enter to open, Esc to cancel)',
|
|
114
114
|
},
|
|
115
115
|
'chats.use_resume': {
|
|
116
|
-
ru: '\n
|
|
117
|
-
en: '\
|
|
116
|
+
ru: '\nДля продолжения используй /resume-id <id> (полный id в скобках).\n',
|
|
117
|
+
en: '\nTo continue use /resume-id <id> (the full id in brackets).\n',
|
|
118
118
|
},
|
|
119
119
|
'chats.none_hint': {
|
|
120
120
|
ru: 'Чатов не найдено. Возможно, сайдбар свёрнут или селекторы устарели.',
|
|
@@ -128,22 +128,6 @@ export const selfReviewMessages = {
|
|
|
128
128
|
ru: ' Системный промпт НЕ пересылается (он уже в начале чата). Включить: /config set browser.resendPromptOnResume true.\n',
|
|
129
129
|
en: ' The system prompt is NOT resent (it is already at the start of the chat). Enable: /config set browser.resendPromptOnResume true.\n',
|
|
130
130
|
},
|
|
131
|
-
'chats.resume_usage': {
|
|
132
|
-
ru: 'Использование: /resume <n> (или /chats для списка)',
|
|
133
|
-
en: 'Usage: /resume <n> (or /chats for the list)',
|
|
134
|
-
},
|
|
135
|
-
'chats.need_number': {
|
|
136
|
-
ru: 'Нужен номер из /chats.',
|
|
137
|
-
en: 'A number from /chats is required.',
|
|
138
|
-
},
|
|
139
|
-
'chats.run_chats_first': {
|
|
140
|
-
ru: 'Сначала выполни /chats.',
|
|
141
|
-
en: 'Run /chats first.',
|
|
142
|
-
},
|
|
143
|
-
'chats.no_n': {
|
|
144
|
-
ru: 'Нет чата №{n}. Всего: {total}.',
|
|
145
|
-
en: 'No chat #{n}. Total: {total}.',
|
|
146
|
-
},
|
|
147
131
|
'chats.opening': { ru: 'Открываю: {v}', en: 'Opening: {v}' },
|
|
148
132
|
'chats.prompt_will_resend': {
|
|
149
133
|
ru: ' Системный промпт будет переслан на следующей задаче.\n',
|
package/dist/i18n/spinner.js
CHANGED
|
@@ -30,6 +30,11 @@ export const spinnerMessages = {
|
|
|
30
30
|
ru: 'Напишите задачу и нажмите Enter. /help — команды, Ctrl+J — новая строка.',
|
|
31
31
|
en: 'Type a task and press Enter. /help for commands, Ctrl+J for a new line.',
|
|
32
32
|
},
|
|
33
|
+
// One-line key hint UNDER the input box (full-screen mode).
|
|
34
|
+
'editor.keys_hint': {
|
|
35
|
+
ru: 'выделение — Shift+протяжка · колесо — скролл · PgUp/PgDn листают · Home/End — в начало/конец',
|
|
36
|
+
en: 'select — Shift+drag · wheel — scroll · PgUp/PgDn scroll · Home/End top/bottom',
|
|
37
|
+
},
|
|
33
38
|
// Reverse search (Ctrl+R) status line. The query may be empty.
|
|
34
39
|
'editor.search_prompt': {
|
|
35
40
|
ru: '(обратный поиск) `{q}` — Ctrl+R дальше, Enter принять, Esc отмена',
|
package/dist/index.js
CHANGED
|
@@ -155,7 +155,7 @@ if (jsonMode) {
|
|
|
155
155
|
// ---------- session persistence ----------
|
|
156
156
|
// We store sessions (DeepSeek chats) in ~/.zames/.sessions so they survive
|
|
157
157
|
// a restart and are available for explicit restore (--resume-last,
|
|
158
|
-
// --chat <id>, /resume <
|
|
158
|
+
// --chat <id>, /resume-id <id>). They are NOT brought up automatically on launch.
|
|
159
159
|
// Previously there was a single last-chat.json file that got lost when the
|
|
160
160
|
// project changed and gave no list of sessions to restore.
|
|
161
161
|
function saveLastChat(id, workdir = '', title = '') {
|
|
@@ -163,7 +163,7 @@ function saveLastChat(id, workdir = '', title = '') {
|
|
|
163
163
|
return;
|
|
164
164
|
// Tag the session with the ACTIVE provider: a chat id is only meaningful to
|
|
165
165
|
// the backend that created it, so a later resume must open it in the same
|
|
166
|
-
// one (or refuse). Without this a /provider switch made
|
|
166
|
+
// one (or refuse). Without this a /provider switch made a resume try to open a
|
|
167
167
|
// qwen id in DeepSeek (and vice versa).
|
|
168
168
|
saveSession({ id, workdir, title, provider: config.provider });
|
|
169
169
|
}
|
|
@@ -437,6 +437,8 @@ ${theme.bold(t('help.while_working'))}
|
|
|
437
437
|
${t('help.key.attach')}
|
|
438
438
|
${t('help.key.esc')}
|
|
439
439
|
${t('help.key.bang')}
|
|
440
|
+
${t('help.key.scroll')}
|
|
441
|
+
${t('help.key.wheel')}
|
|
440
442
|
|
|
441
443
|
${theme.bold(t('help.commands'))}
|
|
442
444
|
|
|
@@ -444,7 +446,6 @@ ${theme.bold(t('help.sec.session'))}
|
|
|
444
446
|
${t('help.cmd.new')}
|
|
445
447
|
${t('help.cmd.sessions')}
|
|
446
448
|
${t('help.cmd.chats')}
|
|
447
|
-
${t('help.cmd.resume')}
|
|
448
449
|
${t('help.cmd.resume_id')}
|
|
449
450
|
${t('help.cmd.last')}
|
|
450
451
|
${t('help.cmd.retry')}
|
|
@@ -1005,7 +1006,10 @@ async function main() {
|
|
|
1005
1006
|
// `--resend-prompt` or `browser.resendPromptOnResume` forces it (e.g. when
|
|
1006
1007
|
// the prompt/tools changed).
|
|
1007
1008
|
const promptOnResume = () => resendPrompt || config.browser.resendPromptOnResume === true;
|
|
1008
|
-
|
|
1009
|
+
// Assigned by the /chats handler before it is read; no initial value needed
|
|
1010
|
+
// (the empty-array default was flagged as a useless assignment once the
|
|
1011
|
+
// /resume <n> handler that read it was removed).
|
|
1012
|
+
let lastChats;
|
|
1009
1013
|
let currentChatId = null;
|
|
1010
1014
|
// The last task the operator sent (used by /retry). Plain text only.
|
|
1011
1015
|
let lastTaskText = '';
|
|
@@ -1079,7 +1083,7 @@ async function main() {
|
|
|
1079
1083
|
total: String(items.length),
|
|
1080
1084
|
});
|
|
1081
1085
|
};
|
|
1082
|
-
// Re-inject a persisted checklist after /resume or the startup restore.
|
|
1086
|
+
// Re-inject a persisted checklist after /resume-id or the startup restore.
|
|
1083
1087
|
// The list is REPLACED (not merged): a resumed chat with no saved checklist
|
|
1084
1088
|
// must clear the previous chat's tasks, otherwise the status line keeps
|
|
1085
1089
|
// showing them for a chat that never had them.
|
|
@@ -1232,17 +1236,18 @@ async function main() {
|
|
|
1232
1236
|
});
|
|
1233
1237
|
};
|
|
1234
1238
|
const buildPrompt = () => {
|
|
1235
|
-
let tail;
|
|
1236
1239
|
if (reviewMode) {
|
|
1237
|
-
|
|
1240
|
+
// Review mode is rare and MEANINGFUL, so it stays in the prompt.
|
|
1241
|
+
return (theme.prompt('❯ ') +
|
|
1242
|
+
theme.dim('(') +
|
|
1238
1243
|
theme.warn(t('prompt.review')) +
|
|
1239
|
-
|
|
1240
|
-
|
|
1244
|
+
theme.dim(':') +
|
|
1245
|
+
theme.dir(reviewMode.snapName) +
|
|
1246
|
+
theme.dim(') '));
|
|
1241
1247
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
return theme.prompt('❯ ') + tail + theme.dim(' › ');
|
|
1248
|
+
// No working-directory tail: it ate input width and is already in the
|
|
1249
|
+
// header / /pwd. A bare prompt keeps room for the task text.
|
|
1250
|
+
return theme.prompt('❯ ') + theme.dim('› ');
|
|
1246
1251
|
};
|
|
1247
1252
|
await refreshDynamicCommands(currentWorkdir);
|
|
1248
1253
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
@@ -1596,6 +1601,8 @@ async function main() {
|
|
|
1596
1601
|
};
|
|
1597
1602
|
try {
|
|
1598
1603
|
ed.start();
|
|
1604
|
+
// The key/selection hint is now a PERSISTENT row under the input box
|
|
1605
|
+
// (FullScreenApp), so the old one-time printAbove is redundant.
|
|
1599
1606
|
}
|
|
1600
1607
|
catch (e) {
|
|
1601
1608
|
// A TUI mount failure must not kill the agent. We surface it and let the
|
|
@@ -1891,7 +1898,7 @@ async function main() {
|
|
|
1891
1898
|
});
|
|
1892
1899
|
}
|
|
1893
1900
|
// Open a chat by its ChatInfo and print the restored dialogue. Shared by
|
|
1894
|
-
// /resume and the interactive /chats list, so both paths behave identically
|
|
1901
|
+
// /resume-id and the interactive /chats list, so both paths behave identically
|
|
1895
1902
|
// (todos reloaded, prompt-resend decision, transcript event).
|
|
1896
1903
|
const openResumedChat = async (pick) => {
|
|
1897
1904
|
if (editor)
|
|
@@ -2007,16 +2014,19 @@ async function main() {
|
|
|
2007
2014
|
}
|
|
2008
2015
|
else {
|
|
2009
2016
|
let tail;
|
|
2017
|
+
// The non-TTY fallback prompt mirrors buildPrompt(): bare, no dir.
|
|
2010
2018
|
if (reviewMode) {
|
|
2011
2019
|
tail =
|
|
2012
|
-
theme.
|
|
2020
|
+
theme.dim('(') +
|
|
2021
|
+
theme.warn(t('prompt.review')) +
|
|
2013
2022
|
theme.dim(':') +
|
|
2014
|
-
theme.dir(reviewMode.snapName)
|
|
2023
|
+
theme.dir(reviewMode.snapName) +
|
|
2024
|
+
theme.dim(') ');
|
|
2015
2025
|
}
|
|
2016
2026
|
else {
|
|
2017
|
-
tail = theme.
|
|
2027
|
+
tail = theme.dim('› ');
|
|
2018
2028
|
}
|
|
2019
|
-
input = await promptOnce(theme.prompt('❯ ') + tail
|
|
2029
|
+
input = await promptOnce(theme.prompt('❯ ') + tail);
|
|
2020
2030
|
}
|
|
2021
2031
|
}
|
|
2022
2032
|
catch {
|
|
@@ -2318,8 +2328,8 @@ async function main() {
|
|
|
2318
2328
|
continue;
|
|
2319
2329
|
}
|
|
2320
2330
|
console.log(theme.system(t('chats.recent')));
|
|
2321
|
-
// The number is the index in the FULL list
|
|
2322
|
-
//
|
|
2331
|
+
// The number is the index in the FULL list (a filter only narrows
|
|
2332
|
+
// the VIEW), and the full id in brackets is what /resume-id takes.
|
|
2323
2333
|
for (const c of shown) {
|
|
2324
2334
|
const idx = lastChats.indexOf(c);
|
|
2325
2335
|
const n = String(idx + 1).padStart(2, ' ');
|
|
@@ -2338,29 +2348,6 @@ async function main() {
|
|
|
2338
2348
|
}
|
|
2339
2349
|
continue;
|
|
2340
2350
|
}
|
|
2341
|
-
if (lower === '/resume' || lower.startsWith('/resume ')) {
|
|
2342
|
-
const arg = trimmed.slice(7).trim();
|
|
2343
|
-
if (!arg) {
|
|
2344
|
-
console.error(theme.error(t('chats.resume_usage')));
|
|
2345
|
-
continue;
|
|
2346
|
-
}
|
|
2347
|
-
const n = Number(arg);
|
|
2348
|
-
if (!Number.isFinite(n) || n < 1) {
|
|
2349
|
-
console.error(theme.error(t('chats.need_number')));
|
|
2350
|
-
continue;
|
|
2351
|
-
}
|
|
2352
|
-
if (!lastChats.length) {
|
|
2353
|
-
console.error(theme.error(t('chats.run_chats_first')));
|
|
2354
|
-
continue;
|
|
2355
|
-
}
|
|
2356
|
-
const pick = lastChats[n - 1];
|
|
2357
|
-
if (!pick) {
|
|
2358
|
-
console.error(theme.error(t('chats.no_n', { n, total: lastChats.length })));
|
|
2359
|
-
continue;
|
|
2360
|
-
}
|
|
2361
|
-
await openResumedChat(pick);
|
|
2362
|
-
continue;
|
|
2363
|
-
}
|
|
2364
2351
|
if (lower === '/provider' || lower.startsWith('/provider ')) {
|
|
2365
2352
|
const arg = trimmed.slice('/provider'.length).trim().toLowerCase();
|
|
2366
2353
|
if (!arg || arg === 'status') {
|
package/dist/input/suggest.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// editor state: given the buffer text and the command list these decide which
|
|
4
4
|
// commands to show, the visible window, and the Tab completion. Split out
|
|
5
5
|
// (C3) so the fuzzy match can be unit-tested without driving the editor.
|
|
6
|
-
import { SUGGEST_PAGE, isSubsequence } from './layout.js';
|
|
6
|
+
import { SUGGEST_PAGE, isSubsequence, visLen, } from './layout.js';
|
|
7
7
|
/**
|
|
8
8
|
* The commands matching the current buffer. Shown only when the line starts
|
|
9
9
|
* with «/» and is a single short word. The match is fuzzy: an exact prefix
|
|
@@ -62,13 +62,28 @@ export function suggestWindow(sugg, offset) {
|
|
|
62
62
|
* (0 when no list). Full-screen mode uses this to RESERVE space above the
|
|
63
63
|
* input panel so opening the list does not shift the layout (the operator saw
|
|
64
64
|
* the whole screen jump when `/chats` or `/resume` hints appeared).
|
|
65
|
+
*
|
|
66
|
+
* With `cols > 0` a row whose label+description is WIDER than the terminal is
|
|
67
|
+
* counted as the multiple rows it actually wraps to; counting one row per
|
|
68
|
+
* command under-reserved the panel and the caret drifted on a narrow terminal
|
|
69
|
+
* (the command list carries descriptions up to ~60 chars).
|
|
65
70
|
*/
|
|
66
|
-
export function suggestRowCount(buf, commands) {
|
|
71
|
+
export function suggestRowCount(buf, commands, cols = 0) {
|
|
67
72
|
const sugg = matchSlashCommands(buf, commands);
|
|
68
73
|
if (!sugg.length)
|
|
69
74
|
return 0;
|
|
70
|
-
const { moreCount } = suggestWindow(sugg, 0);
|
|
71
|
-
|
|
75
|
+
const { shown, moreCount } = suggestWindow(sugg, 0);
|
|
76
|
+
const width = Math.max(1, cols);
|
|
77
|
+
let rows = 0;
|
|
78
|
+
for (const c of shown) {
|
|
79
|
+
const label = c.hint ? c.name + ' ' + c.hint : c.name;
|
|
80
|
+
// 3 = ' ▸ ' prefix, 2 = the gap before the description.
|
|
81
|
+
const textWidth = 3 + visLen(label) + 2 + visLen(c.description);
|
|
82
|
+
rows += cols > 0 ? Math.max(1, Math.ceil(textWidth / width)) : 1;
|
|
83
|
+
}
|
|
84
|
+
if (moreCount > 0)
|
|
85
|
+
rows += 1;
|
|
86
|
+
return rows;
|
|
72
87
|
}
|
|
73
88
|
/**
|
|
74
89
|
* Tab completion. With an explicit selection, insert that command. With a
|
package/dist/theme.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { contentWidth } from './width.js';
|
|
3
|
+
// Respect NO_COLOR (https://no-color.org): when the operator sets it (any
|
|
4
|
+
// non-empty value) drop ALL SGR codes so the output can be piped into a file
|
|
5
|
+
// or a log viewer without escape noise. chalk 5 only auto-detects TTY/CI, it
|
|
6
|
+
// does NOT read NO_COLOR, so the level is set explicitly here — the one place
|
|
7
|
+
// the palette is built, so every consumer (theme, spinner, Ink Text colors
|
|
8
|
+
// that go through chalk) is covered. chalk.level is the documented override.
|
|
9
|
+
if (process.env.NO_COLOR) {
|
|
10
|
+
chalk.level = 0;
|
|
11
|
+
}
|
|
3
12
|
// A calm palette for a dark terminal.
|
|
4
13
|
// Muted, slightly "faded" tones without high saturation
|
|
5
14
|
// (in the spirit of Tokyo Night / Nord). The goal is comfortable reading of
|
package/dist/tui/ConfigMenu.js
CHANGED
|
@@ -47,6 +47,12 @@ export function ConfigMenuApp(opts) {
|
|
|
47
47
|
const [editBuf, setEditBuf] = useState('');
|
|
48
48
|
const [pendingReset, setPendingReset] = useState(null);
|
|
49
49
|
const [message, setMessage] = useState(null);
|
|
50
|
+
// `get`/`set` mutate the LIVE config object in place, so React has no state
|
|
51
|
+
// change to react to and the row kept the OLD value on screen after a
|
|
52
|
+
// toggle/cycle (the operator saw «ru» while the config already held «en»).
|
|
53
|
+
// Bump this counter after every mutation to force a re-render.
|
|
54
|
+
const [version, setVersion] = useState(0);
|
|
55
|
+
const bump = () => setVersion((v) => v + 1);
|
|
50
56
|
const visible = useMemo(() => {
|
|
51
57
|
const q = filterQuery.trim().toLowerCase();
|
|
52
58
|
if (!q)
|
|
@@ -78,6 +84,7 @@ export function ConfigMenuApp(opts) {
|
|
|
78
84
|
}
|
|
79
85
|
try {
|
|
80
86
|
set(f, raw);
|
|
87
|
+
bump();
|
|
81
88
|
setMessage(t('cfg.menu.saved', {
|
|
82
89
|
v: f.path,
|
|
83
90
|
value: displayValue(f, get(f.path), t),
|
|
@@ -93,6 +100,7 @@ export function ConfigMenuApp(opts) {
|
|
|
93
100
|
return;
|
|
94
101
|
if (f.type === 'boolean') {
|
|
95
102
|
set(f, get(f.path) === true ? 'false' : 'true');
|
|
103
|
+
bump();
|
|
96
104
|
setMessage(null);
|
|
97
105
|
return;
|
|
98
106
|
}
|
|
@@ -112,6 +120,7 @@ export function ConfigMenuApp(opts) {
|
|
|
112
120
|
const base = idx < 0 ? 0 : idx;
|
|
113
121
|
const next = f.values[(base + dir + f.values.length) % f.values.length];
|
|
114
122
|
set(f, next);
|
|
123
|
+
bump();
|
|
115
124
|
setMessage(null);
|
|
116
125
|
};
|
|
117
126
|
/** ←/→ acts on the focused row: cycle an enum, toggle a boolean. */
|
|
@@ -123,6 +132,7 @@ export function ConfigMenuApp(opts) {
|
|
|
123
132
|
cycleEnum(f, dir);
|
|
124
133
|
else if (f.type === 'boolean') {
|
|
125
134
|
set(f, get(f.path) === true ? 'false' : 'true');
|
|
135
|
+
bump();
|
|
126
136
|
setMessage(null);
|
|
127
137
|
}
|
|
128
138
|
};
|
|
@@ -229,6 +239,7 @@ export function ConfigMenuApp(opts) {
|
|
|
229
239
|
return;
|
|
230
240
|
if (pendingReset === f.path) {
|
|
231
241
|
reset(f);
|
|
242
|
+
bump();
|
|
232
243
|
setMessage(t('cfg.reset', { v: f.path }));
|
|
233
244
|
setPendingReset(null);
|
|
234
245
|
}
|
|
@@ -239,29 +250,51 @@ export function ConfigMenuApp(opts) {
|
|
|
239
250
|
return;
|
|
240
251
|
}
|
|
241
252
|
});
|
|
242
|
-
// Windowed list: show a slice around the cursor
|
|
253
|
+
// Windowed list: show a slice around the cursor. The window is measured in
|
|
254
|
+
// RENDERED ROWS, not items: each new group adds a header row, AND a label
|
|
255
|
+
// long enough to wrap takes more than one row. Counting one row per item
|
|
256
|
+
// let the block grow taller than the terminal and the top border/title
|
|
257
|
+
// scrolled off ("cut off at the top") on a narrow terminal.
|
|
243
258
|
const termRows = rows || 24;
|
|
244
|
-
const
|
|
259
|
+
const maxRows = Math.max(3, termRows - 8);
|
|
260
|
+
const innerCols = Math.max(1, (columns || 80) - 4);
|
|
245
261
|
const shown = useMemo(() => {
|
|
246
|
-
// Walk back from the cursor until the window would overflow.
|
|
247
|
-
let start = Math.min(cursor, Math.max(0, visible.length - 1));
|
|
248
262
|
const groupStart = (i) => i === 0 || visible[i].groupKey !== visible[i - 1].groupKey;
|
|
249
|
-
|
|
263
|
+
// Rendered height of one item: 1 base row + wrapped extra rows. The label
|
|
264
|
+
// plus the value cell plus the path hint is approximated by the raw label
|
|
265
|
+
// length; over-estimating is safe (it only shortens the window).
|
|
266
|
+
const itemRows = (i) => {
|
|
267
|
+
const f = visible[i];
|
|
268
|
+
const label = f.labelKey ? t(f.labelKey) : f.path;
|
|
269
|
+
return Math.max(1, Math.ceil((label.length + 12) / innerCols));
|
|
270
|
+
};
|
|
271
|
+
const rowsIn = (a, b) => {
|
|
250
272
|
let r = 0;
|
|
251
|
-
for (let i =
|
|
252
|
-
if (
|
|
273
|
+
for (let i = a; i < b; i++) {
|
|
274
|
+
if (groupStart(i))
|
|
253
275
|
r++;
|
|
254
|
-
r
|
|
276
|
+
r += itemRows(i);
|
|
255
277
|
}
|
|
256
278
|
return r;
|
|
257
279
|
};
|
|
258
|
-
|
|
280
|
+
if (!visible.length)
|
|
281
|
+
return { start: 0, end: 0 };
|
|
282
|
+
let start = cursor;
|
|
283
|
+
let end = cursor + 1;
|
|
284
|
+
// Fill downward first, then upward, so the cursor stays visible and the
|
|
285
|
+
// window uses the whole budget.
|
|
286
|
+
while (end < visible.length && rowsIn(start, end + 1) <= maxRows)
|
|
287
|
+
end++;
|
|
288
|
+
while (start > 0 && rowsIn(start - 1, end) <= maxRows)
|
|
259
289
|
start--;
|
|
260
|
-
return { start, end
|
|
261
|
-
}, [cursor, visible,
|
|
290
|
+
return { start, end };
|
|
291
|
+
}, [cursor, visible, maxRows, innerCols, t]);
|
|
262
292
|
const usable = Math.max(1, (columns || 80) - 1);
|
|
263
293
|
void visCols;
|
|
264
294
|
void usable;
|
|
295
|
+
// Referenced so the value-cell recompute below depends on the mutation
|
|
296
|
+
// counter (a pure `get()` call has no reactive dependency of its own).
|
|
297
|
+
void version;
|
|
265
298
|
const items = [];
|
|
266
299
|
let lastGroup = '';
|
|
267
300
|
for (let i = shown.start; i < shown.end; i++) {
|