zames_pro 2.82.140 → 2.82.142
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 +56 -0
- package/README.md +68 -42
- package/SECURITY.md +6 -3
- package/dist/attachments.js +4 -7
- package/dist/backlog.js +2 -2
- package/dist/browser-pure.js +2 -2
- package/dist/browser.js +8 -2
- package/dist/commands.js +7 -3
- package/dist/compact.js +7 -2
- package/dist/config.js +1 -1
- package/dist/context.js +27 -9
- package/dist/glm-browser.js +1 -1
- package/dist/glm-net-capture.js +1 -1
- package/dist/glm-ui.js +0 -20
- package/dist/hooks.js +1 -1
- package/dist/i18n/common.js +16 -11
- package/dist/i18n/config.js +2 -2
- package/dist/i18n/deepseek.js +2 -2
- package/dist/i18n/doctor.js +9 -5
- package/dist/i18n/glm.js +2 -2
- package/dist/i18n/help.js +126 -117
- package/dist/i18n/mcp.js +11 -5
- package/dist/i18n/prompt.js +9 -3
- package/dist/i18n/qwen.js +2 -2
- package/dist/i18n/self-review.js +4 -0
- package/dist/i18n/spinner.js +13 -8
- package/dist/index.js +8 -5
- package/dist/mcp.js +6 -3
- package/dist/prompt.js +16 -1
- package/dist/qwen-browser.js +1 -1
- package/dist/qwen-ui.js +0 -2
- package/dist/run-task.js +1 -1
- package/dist/scheduler.js +1 -1
- package/dist/sessions.js +1 -1
- package/dist/spinner.js +1 -1
- package/dist/status-command.js +6 -2
- package/dist/statusline.js +2 -2
- package/dist/subagent.js +3 -3
- package/dist/system-prompt.js +9 -1
- package/dist/tui/AskQuestion.js +1 -1
- package/dist/tui/FullScreenApp.js +4 -15
- package/dist/tui/LogView.js +1 -1
- package/dist/tui/TuiEditor.js +7 -0
- package/dist/tui/edit.js +1 -1
- package/dist/tui/layout.js +1 -1
- package/dist/tui/widgets.js +1 -25
- package/dist/web.js +1 -1
- package/dist/width.js +1 -1
- package/llms-full.txt +35 -22
- package/llms.txt +4 -2
- package/package.json +2 -2
- package/skills/systematic-debugging/SKILL.md +7 -0
- package/skills/test-driven-development/SKILL.md +5 -0
- package/skills/verification-before-completion/SKILL.md +5 -0
- package/skills/writing-plans/SKILL.md +12 -0
- package/dist/code-blocks.js +0 -54
package/dist/index.js
CHANGED
|
@@ -485,9 +485,9 @@ ${theme.bold(t('help.while_working'))}
|
|
|
485
485
|
${t('help.key.search')}
|
|
486
486
|
${t('help.key.words')}
|
|
487
487
|
${t('help.key.undo')}
|
|
488
|
+
${t('help.key.clear_screen')}
|
|
488
489
|
${t('help.key.slash')}
|
|
489
490
|
${t('help.key.newline')}
|
|
490
|
-
${t('help.key.backslash')}
|
|
491
491
|
${t('help.key.attach')}
|
|
492
492
|
${t('help.key.esc')}
|
|
493
493
|
${t('help.key.bang')}
|
|
@@ -703,7 +703,7 @@ async function resolveWorkdir() {
|
|
|
703
703
|
const dir = explicitDir ? path.resolve(explicitDir) : process.cwd();
|
|
704
704
|
const stat = await fs.stat(dir).catch(() => null);
|
|
705
705
|
if (!stat || !stat.isDirectory()) {
|
|
706
|
-
throw new Error(t('
|
|
706
|
+
throw new Error(t('cd.not_dir', { v: dir }));
|
|
707
707
|
}
|
|
708
708
|
return dir;
|
|
709
709
|
}
|
|
@@ -1063,7 +1063,7 @@ async function main() {
|
|
|
1063
1063
|
console.error(theme.warn(t('mcp.server_error', { name: srv.name, error: srv.error })));
|
|
1064
1064
|
}
|
|
1065
1065
|
for (const w of st.warnings)
|
|
1066
|
-
console.error(theme.warn(w));
|
|
1066
|
+
console.error(theme.warn(t(w.key, w.params)));
|
|
1067
1067
|
}
|
|
1068
1068
|
catch (e) {
|
|
1069
1069
|
console.error(theme.warn(t('mcp.load_failed', { v: e.message })));
|
|
@@ -1224,6 +1224,7 @@ async function main() {
|
|
|
1224
1224
|
const recoverFromContextLimit = async () => {
|
|
1225
1225
|
const res = await performCompact({
|
|
1226
1226
|
browser,
|
|
1227
|
+
providerLabel: providerLabel(config.provider),
|
|
1227
1228
|
currentChatId,
|
|
1228
1229
|
workdir: currentWorkdir,
|
|
1229
1230
|
locale: currentLocale,
|
|
@@ -2970,7 +2971,7 @@ async function main() {
|
|
|
2970
2971
|
// finding it meant /chats (a network fetch) or copying the id.
|
|
2971
2972
|
const last = loadLastSession(currentWorkdir, config.provider);
|
|
2972
2973
|
if (!last || !last.id) {
|
|
2973
|
-
console.error(theme.error(t('sessions.
|
|
2974
|
+
console.error(theme.error(t('sessions.none_here')));
|
|
2974
2975
|
continue;
|
|
2975
2976
|
}
|
|
2976
2977
|
if (editor)
|
|
@@ -3253,7 +3254,7 @@ async function main() {
|
|
|
3253
3254
|
}
|
|
3254
3255
|
}
|
|
3255
3256
|
for (const w of st.warnings)
|
|
3256
|
-
console.log(theme.warn(' ' + w));
|
|
3257
|
+
console.log(theme.warn(' ' + t(w.key, w.params)));
|
|
3257
3258
|
continue;
|
|
3258
3259
|
}
|
|
3259
3260
|
if (lower === '/skills') {
|
|
@@ -3735,6 +3736,7 @@ async function main() {
|
|
|
3735
3736
|
// paths stay identical (same retries, same fallback, same resend).
|
|
3736
3737
|
const res = await performCompact({
|
|
3737
3738
|
browser,
|
|
3739
|
+
providerLabel: providerLabel(config.provider),
|
|
3738
3740
|
currentChatId,
|
|
3739
3741
|
workdir: currentWorkdir,
|
|
3740
3742
|
locale: currentLocale,
|
|
@@ -4198,6 +4200,7 @@ async function main() {
|
|
|
4198
4200
|
? async () => {
|
|
4199
4201
|
const res = await performCompact({
|
|
4200
4202
|
browser,
|
|
4203
|
+
providerLabel: providerLabel(config.provider),
|
|
4201
4204
|
currentChatId,
|
|
4202
4205
|
workdir: currentWorkdir,
|
|
4203
4206
|
locale: currentLocale,
|
package/dist/mcp.js
CHANGED
|
@@ -110,7 +110,7 @@ export function normalizeServerConfig(raw) {
|
|
|
110
110
|
return out;
|
|
111
111
|
}
|
|
112
112
|
/** Candidate config paths, ordered from global to project-local. */
|
|
113
|
-
|
|
113
|
+
async function discoverConfigFiles(workdir) {
|
|
114
114
|
const path = await import('path');
|
|
115
115
|
const os = await import('os');
|
|
116
116
|
const fsMod = await import('fs/promises');
|
|
@@ -127,7 +127,7 @@ export async function discoverConfigFiles(workdir) {
|
|
|
127
127
|
return out;
|
|
128
128
|
}
|
|
129
129
|
/** Read and merge every config file. Later files override earlier ones. */
|
|
130
|
-
|
|
130
|
+
async function loadConfigFiles(files) {
|
|
131
131
|
const merged = {};
|
|
132
132
|
const fsMod = await import('fs/promises');
|
|
133
133
|
for (const f of files) {
|
|
@@ -486,7 +486,10 @@ export async function createMcpPool(opts) {
|
|
|
486
486
|
status: () => ({
|
|
487
487
|
servers: infos,
|
|
488
488
|
toolCount: tools.length,
|
|
489
|
-
warnings: floating.map((n) =>
|
|
489
|
+
warnings: floating.map((n) => ({
|
|
490
|
+
key: 'mcp.floating',
|
|
491
|
+
params: { v: n },
|
|
492
|
+
})),
|
|
490
493
|
}),
|
|
491
494
|
close: async () => {
|
|
492
495
|
if (closed)
|
package/dist/prompt.js
CHANGED
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
// pasted text in the markers \x1b[200~ … \x1b[201~, so we know for sure
|
|
11
11
|
// that this is a paste, not keyboard typing, and Enter inside it is not
|
|
12
12
|
// treated as submission.
|
|
13
|
+
//
|
|
14
|
+
// A `data` chunk is a stream of UTF-8 BYTES and a terminal hands them over in
|
|
15
|
+
// whatever sizes the pty chose: an emoji (4 bytes) or any other non-ASCII
|
|
16
|
+
// character can be split between two reads. `Buffer#toString('utf-8')` decodes
|
|
17
|
+
// every read on its own, so a split character became a run of U+FFFD — the
|
|
18
|
+
// operator's answer (a login, a path, a Cyrillic task) arrived corrupted. The
|
|
19
|
+
// decoder below holds back an INCOMPLETE trailing sequence until the bytes
|
|
20
|
+
// that finish it arrive, and every read (keys, paste, the pasted tail) goes
|
|
21
|
+
// through it. The non-TTY path instead CONCATENATES the raw buffers and decodes
|
|
22
|
+
// once at EOF, so a split there heals by itself.
|
|
23
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
13
24
|
export async function promptOnce(question) {
|
|
14
25
|
const stdin = process.stdin;
|
|
15
26
|
const stdout = process.stdout;
|
|
@@ -71,8 +82,12 @@ export async function promptOnce(question) {
|
|
|
71
82
|
line = line.slice(0, cursor) + clean + line.slice(cursor);
|
|
72
83
|
cursor += clean.length;
|
|
73
84
|
};
|
|
85
|
+
// One decoder for the WHOLE read loop: it keeps the bytes of a character
|
|
86
|
+
// that was split between two `data` events and prepends them to the next
|
|
87
|
+
// read, instead of turning both halves into U+FFFD.
|
|
88
|
+
const decoder = new StringDecoder('utf8');
|
|
74
89
|
const onData = (buf) => {
|
|
75
|
-
let s =
|
|
90
|
+
let s = decoder.write(buf);
|
|
76
91
|
// Fallback for terminals without bracketed paste: if the whole chunk is
|
|
77
92
|
// a "bare" newline (one byte), then Enter was pressed → submit.
|
|
78
93
|
// If newlines came TOGETHER with other text in one chunk — that's a
|
package/dist/qwen-browser.js
CHANGED
|
@@ -35,7 +35,7 @@ import { extractAnswer, extractTokenUsage, isFinishedWithoutAnswer, isRateLimitT
|
|
|
35
35
|
// contexts on ONE --user-data-dir cannot coexist (SingletonLock), and keeping
|
|
36
36
|
// Qwen's cookies apart also means switching providers never logs the other
|
|
37
37
|
// one out.
|
|
38
|
-
|
|
38
|
+
const QWEN_USER_DATA_DIR = path.join(os.homedir(), '.zames', 'qwen-profile');
|
|
39
39
|
// Escape a string for use inside a RegExp (the model id may carry dots).
|
|
40
40
|
function escapeRegExp(s) {
|
|
41
41
|
return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
package/dist/qwen-ui.js
CHANGED
|
@@ -169,8 +169,6 @@ export const LOGIN_SELECTORS = [
|
|
|
169
169
|
'input[autocomplete="username"]',
|
|
170
170
|
];
|
|
171
171
|
// ---------- service statuses / errors ----------
|
|
172
|
-
// A generic "generating" label that must never be taken for an answer.
|
|
173
|
-
export const STATUS_RE = /^(thinking|searching|generating|thinking completed|остановить|stop)[\s.…]*$/i;
|
|
174
172
|
// Qwen's rate-limit / busy notices (best-effort; the toast may be short-lived).
|
|
175
173
|
export const RATE_LIMIT_RE = /too many requests|rate limit|too frequent|please try again later|слишком часто/i;
|
|
176
174
|
export const SERVER_BUSY_RE = /server is busy|service unavailable|try again later/i;
|
package/dist/run-task.js
CHANGED
|
@@ -123,7 +123,7 @@ async function promptOnce(question) {
|
|
|
123
123
|
// Enter sends the buffer to onQueue; an empty Enter is ignored. Backspace,
|
|
124
124
|
// Ctrl+U, Esc sequences (arrows/Home/End/Delete are ignored) and bracketed
|
|
125
125
|
// paste are supported. Returns a cleanup function.
|
|
126
|
-
|
|
126
|
+
function watchInput({ onEscape, onChange, onQueue, } = {}) {
|
|
127
127
|
const stdin = process.stdin;
|
|
128
128
|
if (!stdin.isTTY || !stdin.setRawMode)
|
|
129
129
|
return () => { };
|
package/dist/scheduler.js
CHANGED
|
@@ -26,7 +26,7 @@ export function parseInterval(text) {
|
|
|
26
26
|
return ms;
|
|
27
27
|
}
|
|
28
28
|
/** Largest delay setInterval accepts (32-bit signed milliseconds). */
|
|
29
|
-
|
|
29
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
30
30
|
// Render an interval back to the shortest human form (used in listings).
|
|
31
31
|
// Non-finite input (a cron job whose next fire could not be computed sets
|
|
32
32
|
// nextAt to Infinity) renders as "-" instead of "Infinityh".
|
package/dist/sessions.js
CHANGED
|
@@ -68,7 +68,7 @@ function safeName(id) {
|
|
|
68
68
|
// Save/update a session. workdir is needed so that when launched from the same
|
|
69
69
|
// project we restore exactly its last chat.
|
|
70
70
|
/** Hard cap on the todo checklist stored with a session. */
|
|
71
|
-
|
|
71
|
+
const SESSION_TODOS_CAP = 1000;
|
|
72
72
|
export function saveSession({ id, title = '', workdir = '', todos, provider, }) {
|
|
73
73
|
if (!id)
|
|
74
74
|
return null;
|
package/dist/spinner.js
CHANGED
|
@@ -22,7 +22,7 @@ export function stripEllipsis(phrase) {
|
|
|
22
22
|
// (3 dots) so the trailing hint does not "jump" when the phase changes. Kept in
|
|
23
23
|
// one place so the two status lines animate identically.
|
|
24
24
|
export const DOTS = ['', '.', '..', '...'];
|
|
25
|
-
|
|
25
|
+
const DOTS_PAD = ' ';
|
|
26
26
|
// The colored dot string for a phase (brown, matching the base text).
|
|
27
27
|
export function renderDots(n) {
|
|
28
28
|
const d = DOTS[n] ?? '';
|
package/dist/status-command.js
CHANGED
|
@@ -7,7 +7,7 @@ export function formatStatus(s, t = translate('en')) {
|
|
|
7
7
|
const onoff = (b) => (b ? t('common.on') : t('common.off'));
|
|
8
8
|
const lines = [];
|
|
9
9
|
lines.push(t('status.workdir', { v: s.workdir }));
|
|
10
|
-
lines.push(t('status.review_mode', { v: s.reviewMode ||
|
|
10
|
+
lines.push(t('status.review_mode', { v: s.reviewMode || no }));
|
|
11
11
|
if (s.reviewMode && s.origDir) {
|
|
12
12
|
lines.push(t('status.orig_dir', { v: s.origDir }));
|
|
13
13
|
}
|
|
@@ -36,7 +36,11 @@ export function formatStatus(s, t = translate('en')) {
|
|
|
36
36
|
// A NEGATIVE tool count is nonsense (a broken MCP server status, a
|
|
37
37
|
// hand-edited value): "MCP tools: -2" reads like a real measurement, the
|
|
38
38
|
// same rule the token counter below already follows.
|
|
39
|
-
|
|
39
|
+
// `none` ("(none)") would claim that the tools themselves are absent;
|
|
40
|
+
// with no MCP pool configured the COUNT is simply unknown.
|
|
41
|
+
const mcpCount = s.mcpToolCount === null || s.mcpToolCount < 0
|
|
42
|
+
? t('common.unknown')
|
|
43
|
+
: s.mcpToolCount;
|
|
40
44
|
lines.push(t('status.mcp', { v: mcpCount }));
|
|
41
45
|
// A NEGATIVE count is nonsense (a broken provider counter): show "unknown"
|
|
42
46
|
// rather than a number that reads like a real measurement.
|
package/dist/statusline.js
CHANGED
|
@@ -9,10 +9,10 @@ import { spawn } from 'node:child_process';
|
|
|
9
9
|
// hidden), never an error in the UI.
|
|
10
10
|
// Longest status text that still fits a typical terminal row together with
|
|
11
11
|
// the rest of the status bar.
|
|
12
|
-
|
|
12
|
+
const STATUS_BADGE_MAX = 80;
|
|
13
13
|
/** Clip a status badge to STATUS_BADGE_MAX with an ellipsis (never splits a
|
|
14
14
|
* surrogate pair). */
|
|
15
|
-
|
|
15
|
+
function clipBadge(text, max = STATUS_BADGE_MAX) {
|
|
16
16
|
const s = String(text ?? '');
|
|
17
17
|
if (s.length <= max)
|
|
18
18
|
return s;
|
package/dist/subagent.js
CHANGED
|
@@ -14,11 +14,11 @@ import { markInternalChat } from './sessions.js';
|
|
|
14
14
|
// subagents would need a second page and a shared send scheduler (a change to
|
|
15
15
|
// browser.ts, which owns the live Playwright context) — out of scope here.
|
|
16
16
|
/** Hard cap on iterations for one subagent run. */
|
|
17
|
-
|
|
17
|
+
const SUBAGENT_MAX_ITER = 30;
|
|
18
18
|
// The report is fed back into the PARENT chat as a tool result, so a runaway
|
|
19
19
|
// subagent must not be able to blow up the parent's context with one answer.
|
|
20
20
|
// The tail is trimmed with a marker, not silently dropped.
|
|
21
|
-
|
|
21
|
+
const SUBAGENT_REPORT_CAP = 20_000;
|
|
22
22
|
/** Cap a subagent report before it becomes a parent-context tool result. */
|
|
23
23
|
export function capReport(text, cap = SUBAGENT_REPORT_CAP) {
|
|
24
24
|
if (text.length <= cap)
|
|
@@ -41,7 +41,7 @@ export function capReport(text, cap = SUBAGENT_REPORT_CAP) {
|
|
|
41
41
|
* standard system prompt itself (tool list + call format), and the model needs
|
|
42
42
|
* exactly that to work. The persona only adds role + output contract.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
function subagentPersona(type) {
|
|
45
45
|
const base = 'You are a SUBAGENT launched by a main coding agent to complete ONE ' +
|
|
46
46
|
'self-contained sub-task in an ISOLATED chat. You do NOT see the main ' +
|
|
47
47
|
"conversation's history, so rely only on the instructions below and on " +
|
package/dist/system-prompt.js
CHANGED
|
@@ -199,7 +199,7 @@ export function renderContextSection(context) {
|
|
|
199
199
|
// of truth for `### <Tool>` blocks; the prose that cannot be derived
|
|
200
200
|
// ("Choosing the right tool", the call formats) stays hard-coded below.
|
|
201
201
|
// Guarded by test/system-prompt-tools.test.ts.
|
|
202
|
-
|
|
202
|
+
function renderToolSection(tools) {
|
|
203
203
|
return tools
|
|
204
204
|
.map((t2) => `### ${t2.name}\n${t2.description}\nParameters: ${JSON.stringify(t2.parameters)}`)
|
|
205
205
|
.join('\n\n');
|
|
@@ -351,6 +351,11 @@ and the (MCP server: ...) note in its description tells you which server
|
|
|
351
351
|
it belongs to. The JSON Schema in the description lists the exact fields
|
|
352
352
|
(including their descriptions and enums) - follow it.
|
|
353
353
|
|
|
354
|
+
In plan (read-only) mode an MCP tool whose name looks mutating (click,
|
|
355
|
+
type, write, ...) is REMOVED from your tool set, exactly like the
|
|
356
|
+
built-in writers. The list you were given is the whole truth: if a tool
|
|
357
|
+
is not in it, it is not available in this run - do not try to call it.
|
|
358
|
+
|
|
354
359
|
## How to use tools
|
|
355
360
|
|
|
356
361
|
To call ONE tool, respond with ONLY a JSON object (no markdown fences, no extra text):
|
|
@@ -420,6 +425,9 @@ reliably, pass the content in base64 instead of plain text:
|
|
|
420
425
|
|
|
421
426
|
- Write: use 'content_base64' instead of 'content'.
|
|
422
427
|
- Edit: use 'old_base64' / 'new_base64' instead of 'old_string' / 'new_string'.
|
|
428
|
+
- MultiEdit: use 'edits_base64' (base64 of the JSON array) instead of 'edits'.
|
|
429
|
+
- TodoWrite: use 'todos_base64' (base64 of the JSON array) instead of 'todos'.
|
|
430
|
+
- ApplyPatch: use 'patch_base64' instead of 'patch'.
|
|
423
431
|
|
|
424
432
|
base64 alphabet is [A-Za-z0-9+/=] and is not corrupted. Encode with the
|
|
425
433
|
base64 command or in Node via Buffer.from(text).toString('base64').
|
package/dist/tui/AskQuestion.js
CHANGED
|
@@ -91,7 +91,7 @@ export function AskQuestion({ question, options, recommended, allowText, locale
|
|
|
91
91
|
out.push(line);
|
|
92
92
|
return out.length ? out : [''];
|
|
93
93
|
};
|
|
94
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.accent, paddingX: 2, children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + t('askuser.title') }), wrap(question).map((line, i) => (_jsx(Text, { color: UI.text, children: line }, i))), _jsx(Text, { color: "#3a3f4b", children: '─'.repeat(innerCols) }), typing ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.dim, children: t('askuser.your_answer') }), _jsxs(Text, { color: UI.accent, children: ['❯ ', text, '▏'] })] })) : (rows.map((label, i) => {
|
|
94
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.accent, paddingX: 2, children: [_jsx(Text, { color: UI.title, bold: true, children: '◆ ' + t('askuser.title') }), wrap(question).map((line, i) => (_jsx(Text, { color: UI.text, children: line }, i))), _jsx(Text, { color: "#3a3f4b", children: '─'.repeat(innerCols) }), typing ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: UI.dim, children: t('askuser.your_answer') }), _jsx(Text, { color: UI.dim, children: t('askuser.enter_send') }), _jsxs(Text, { color: UI.accent, children: ['❯ ', text, '▏'] })] })) : (rows.map((label, i) => {
|
|
95
95
|
const sel = i === cursor;
|
|
96
96
|
const isRec = i === recommended;
|
|
97
97
|
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: sel ? '❯ ' : ' ' }), _jsx(Text, { color: sel ? UI.accent : undefined, bold: sel, children: label }), isRec ? (_jsx(Text, { color: "#6fd0b0", children: ' (' + t('askuser.recommended') + ')' })) : null] }, i));
|
|
@@ -12,10 +12,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
import { useSyncExternalStore } from 'react';
|
|
13
13
|
import { Box, Text, useWindowSize, useInput } from 'ink';
|
|
14
14
|
import { LogList } from './LogView.js';
|
|
15
|
-
import { windowByRows, totalRows
|
|
15
|
+
import { windowByRows, totalRows } from './log-window.js';
|
|
16
16
|
import { StatusBar } from './StatusBar.js';
|
|
17
17
|
import { InputLine } from './InputLine.js';
|
|
18
|
-
import { contentWidth } from '../width.js';
|
|
19
18
|
import { translate } from '../i18n.js';
|
|
20
19
|
import { suggestRowCount } from '../input/suggest.js';
|
|
21
20
|
import { truncateToWidth, visLen } from '../input/layout.js';
|
|
@@ -69,22 +68,13 @@ export function FullScreenApp({ store, inputProps, }) {
|
|
|
69
68
|
// it half empty. `scroll` is measured in ROWS here (0 = newest at the
|
|
70
69
|
// bottom).
|
|
71
70
|
const all = state.log;
|
|
72
|
-
// The
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// short window, so the input rows fell below the last screen row and the
|
|
76
|
-
// caret drifted under the box while a long multi-line buffer was typed.
|
|
77
|
-
const totalFloor = totalRows(all, cols, 'floor');
|
|
71
|
+
// The scroll bound uses the EXACT row count: it is what the content really
|
|
72
|
+
// renders, so the newest row stays reachable. (Reserving the viewport with
|
|
73
|
+
// the floor is a separate concern, handled in `fullScreenLayout`.)
|
|
78
74
|
const total = totalRows(all, cols);
|
|
79
75
|
const maxScroll = Math.max(0, total - logHeight);
|
|
80
76
|
const scroll = Math.max(0, Math.min(state.scroll, maxScroll));
|
|
81
77
|
const visible = windowByRows(all, logHeight, scroll, cols);
|
|
82
|
-
// Rows the WINDOWED content really needs. The reservation above rounds DOWN
|
|
83
|
-
// per entry (floor), so the difference is the slack the content needs. The
|
|
84
|
-
// box takes at most `logHeight` and at least one row; never more, because
|
|
85
|
-
// that is exactly what pushed the input rows off a short screen.
|
|
86
|
-
const filledRows = visible.reduce((n, e) => n + entryRows(e, cols), 0);
|
|
87
|
-
const logBoxHeight = Math.max(1, Math.min(logHeight, Math.max(filledRows, 1)));
|
|
88
78
|
// Scroll the log with PgUp/PgDn; Home/End jump to the very top/bottom. The
|
|
89
79
|
// input line OWNS Home/End while its buffer is non-empty (they move the
|
|
90
80
|
// caret), so the jump only applies to an EMPTY buffer — which is exactly
|
|
@@ -126,4 +116,3 @@ export function FullScreenApp({ store, inputProps, }) {
|
|
|
126
116
|
cursorTop: fsLayout.cursorTop, prompt: state.prompt, locale: state.locale, commands: state.commands, locked: state.locked }), _jsx(Text, { color: "#5b616e", wrap: "truncate", children: ' ' +
|
|
127
117
|
translate(state.locale)(state.mouseScroll ? 'editor.keys_hint' : 'editor.keys_hint_select') })] }));
|
|
128
118
|
}
|
|
129
|
-
void contentWidth;
|
package/dist/tui/LogView.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Static, Box, Text } from 'ink';
|
|
|
3
3
|
import { theme } from '../theme.js';
|
|
4
4
|
/** One log entry. `raw` items already carry ANSI colors and are printed
|
|
5
5
|
* verbatim; structured items get a themed prefix. */
|
|
6
|
-
|
|
6
|
+
function LogRow({ entry }) {
|
|
7
7
|
if (entry.raw) {
|
|
8
8
|
// Pre-rendered (markdown answer, tool preview). Print as-is; Ink passes
|
|
9
9
|
// embedded ANSI escape sequences through to the terminal.
|
package/dist/tui/TuiEditor.js
CHANGED
|
@@ -134,6 +134,13 @@ export class TuiEditor {
|
|
|
134
134
|
// printAbove), so Ink must not patch them: an unmount (clearScreen)
|
|
135
135
|
// would otherwise restore the native console and lose that routing.
|
|
136
136
|
patchConsole: false,
|
|
137
|
+
// Shift+Enter is INDISTINGUISHABLE from a plain Enter unless the
|
|
138
|
+
// terminal disambiguates escape codes: both arrive as CR. The kitty
|
|
139
|
+
// keyboard protocol makes the modified form a distinct CSI-u
|
|
140
|
+
// sequence, which is what `dispatchKey` maps to a newline.
|
|
141
|
+
// 'auto' probes the terminal (CSI ? u) and stays silent when the
|
|
142
|
+
// terminal does not answer, so an unsupporting one is unaffected.
|
|
143
|
+
kittyKeyboard: { mode: 'auto' },
|
|
137
144
|
alternateScreen: this.alternateScreen,
|
|
138
145
|
});
|
|
139
146
|
this.rendered = true;
|
package/dist/tui/edit.js
CHANGED
|
@@ -292,7 +292,7 @@ export function searchStart(s) {
|
|
|
292
292
|
};
|
|
293
293
|
return searchRefresh(st, true);
|
|
294
294
|
}
|
|
295
|
-
|
|
295
|
+
function searchRefresh(s, resetToNewest) {
|
|
296
296
|
const q = s.searchQuery;
|
|
297
297
|
const index = resetToNewest ? s.history.length - 1 : s.searchIndex;
|
|
298
298
|
for (let i = index; i >= 0; i--) {
|
package/dist/tui/layout.js
CHANGED
|
@@ -7,7 +7,7 @@ import { inputBoxRows } from '../input/layout.js';
|
|
|
7
7
|
/** Rows of the fixed header: the title row + the divider. */
|
|
8
8
|
export const HEADER_ROWS = 2;
|
|
9
9
|
/** Rows of the status bar above the input box. */
|
|
10
|
-
|
|
10
|
+
const STATUS_ROWS = 1;
|
|
11
11
|
/** Rows of the key hint UNDER the input box. */
|
|
12
12
|
export const HINT_ROWS = 1;
|
|
13
13
|
export function fullScreenLayout(input) {
|
package/dist/tui/widgets.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Text } from 'ink';
|
|
3
3
|
export const UI = {
|
|
4
4
|
accent: '#c8b06a',
|
|
5
5
|
accentDim: '#7a6b45',
|
|
@@ -23,31 +23,7 @@ export function Checkbox({ checked, focused, }) {
|
|
|
23
23
|
export function Select({ value, focused, }) {
|
|
24
24
|
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.off, children: '‹ ' }), _jsx(Text, { color: focused ? UI.accent : UI.text, bold: focused, children: value }), _jsx(Text, { color: UI.off, children: ' ›' })] }));
|
|
25
25
|
}
|
|
26
|
-
/** A framed panel (rounded border) with an optional title bar and footer. */
|
|
27
|
-
export function Panel({ title, subtitle, footer, width, children, }) {
|
|
28
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI.border, paddingX: 1, width: width, children: [title ? (_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: UI.title, bold: true, children: title }), subtitle ? _jsx(Text, { color: UI.dim, children: subtitle }) : null] })) : null, title ? (_jsx(Text, { color: "#3a3f4b", children: '─'.repeat(panelInnerCols(width)) })) : null, children, footer ? footer : null] }));
|
|
29
|
-
}
|
|
30
|
-
/** Inner width (minus border + paddingX) the panel divider should span. */
|
|
31
|
-
function panelInnerCols(width) {
|
|
32
|
-
const cols = width || process.stdout.columns || 80;
|
|
33
|
-
return Math.max(1, cols - 6);
|
|
34
|
-
}
|
|
35
|
-
/** A single hint chip: `↑/↓ select`. */
|
|
36
|
-
export function Hint({ keys, label, }) {
|
|
37
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: UI.accent, children: keys }), _jsx(Text, { color: UI.dim, children: ' ' + label })] }));
|
|
38
|
-
}
|
|
39
26
|
/** A row of hint chips separated by ` · `. */
|
|
40
27
|
export function HintBar({ hints, }) {
|
|
41
28
|
return (_jsx(Text, { children: hints.map(([k, l], i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { color: UI.dim, children: ' · ' }) : null, _jsx(Text, { color: UI.accent, children: k }), _jsx(Text, { color: UI.dim, children: ' ' + l })] }, i))) }));
|
|
42
29
|
}
|
|
43
|
-
/**
|
|
44
|
-
* A CODE BLOCK with a syntax-highlighted body and an optional title label.
|
|
45
|
-
*
|
|
46
|
-
* The agent's answers are printed as pre-rendered ANSI (markdown.ts already
|
|
47
|
-
* highlights with cli-highlight), so the live log does NOT use this component —
|
|
48
|
-
* it exists for menus/panels that show a snippet inline (and for a future
|
|
49
|
-
* structured log).
|
|
50
|
-
*/
|
|
51
|
-
export function CodeBlock({ code, lang, highlighted, width, hint, focused, }) {
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: focused ? UI.accent : UI.border, paddingX: 1, width: width, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: UI.dim, children: lang || 'code' }), hint ? _jsx(Text, { color: UI.accent, children: hint }) : null] }), (highlighted || code).split(String.fromCharCode(10)).map((line, i) => (_jsx(Text, { children: line || ' ' }, i)))] }));
|
|
53
|
-
}
|
package/dist/web.js
CHANGED
|
@@ -97,7 +97,7 @@ export function htmlToText(html) {
|
|
|
97
97
|
// accept) into a uint32, or null when it is not one. Rules: 1-4 parts; each
|
|
98
98
|
// part may be hex (0x…), octal (leading 0) or decimal; the LAST part fills all
|
|
99
99
|
// remaining bytes, so `127.1` == 127.0.0.1 and `2130706433` == 127.0.0.1.
|
|
100
|
-
|
|
100
|
+
function parseLegacyIpv4(host) {
|
|
101
101
|
const h = String(host || '').trim();
|
|
102
102
|
if (!h)
|
|
103
103
|
return null;
|
package/dist/width.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// A longer line is harder to read and the eye loses the start of the next
|
|
9
9
|
// line on a wide terminal, so there is a cap.
|
|
10
10
|
// > 0 = an explicit cap (still never wider than the terminal).
|
|
11
|
-
|
|
11
|
+
const DEFAULT_CONTENT_WIDTH = 100;
|
|
12
12
|
let configured = 0;
|
|
13
13
|
/** Set the configured content width (0 or a non-positive value = auto). */
|
|
14
14
|
export function setContentWidth(width) {
|
package/llms-full.txt
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# zames_pro — full documentation for LLMs
|
|
2
2
|
|
|
3
3
|
> Terminal coding agent that drives a real web chat LLM (DeepSeek, Qwen,
|
|
4
|
-
> GLM) through a
|
|
4
|
+
> GLM) through a Playwright browser (DeepSeek and Qwen headless, GLM in a
|
|
5
|
+
> visible window) — no API key required.
|
|
5
6
|
> Package: https://www.npmjs.com/package/zames_pro
|
|
6
7
|
> Source: https://github.com/Viqto0r/zames_pro
|
|
7
8
|
|
|
@@ -111,7 +112,9 @@ Key pieces:
|
|
|
111
112
|
- **Subagents** — the `Task` tool delegates a self-contained sub-task to an
|
|
112
113
|
agent in its own separate chat (isolated context), which returns only a
|
|
113
114
|
report, so heavy research does not flood the main context. On by default;
|
|
114
|
-
|
|
115
|
+
`browser.subagents` turns it off and `browser.maxSubagents` caps the runs
|
|
116
|
+
per TASK (default 3). Calls are sequential, not parallel, and the subtype
|
|
117
|
+
matters: `explore` is read-only, `general` may edit files.
|
|
115
118
|
- **MCP support** — plug in external tool servers (e.g. `@playwright/mcp`).
|
|
116
119
|
- **Scheduling** — `/loop`, `/cron` and `/jobs` repeat tasks on a timer.
|
|
117
120
|
- **Bilingual UI** — Russian / English (`/config lang`).
|
|
@@ -271,8 +274,12 @@ The prompt is a small line editor with persistent history:
|
|
|
271
274
|
- `Ctrl+_` — undo the last edit in the input line (a fat-fingered `Ctrl+U` /
|
|
272
275
|
`Ctrl+K` is recoverable).
|
|
273
276
|
- `Ctrl+U` — clear the line, `Ctrl+K` — delete to end of line, `Ctrl+W` —
|
|
274
|
-
delete the word before the cursor, `Ctrl+←`/`Ctrl+→` — move by words
|
|
275
|
-
|
|
277
|
+
delete the word before the cursor, `Ctrl+←`/`Ctrl+→` — move by words,
|
|
278
|
+
`Ctrl+Delete`/`Alt+Delete` — delete the word after the cursor.
|
|
279
|
+
- `\` + `Enter` and `Ctrl+J` — insert a newline; these two work in
|
|
280
|
+
every terminal. `Ctrl+Enter` and `Shift+Enter` do it too, but only where the
|
|
281
|
+
terminal implements the kitty keyboard protocol: without it the terminal
|
|
282
|
+
sends a PLAIN Enter for Shift+Enter and the message is sent.
|
|
276
283
|
- `/` + `Tab` — slash-command hints and completion.
|
|
277
284
|
- `!command` — run a shell command directly, bypassing the model (like
|
|
278
285
|
Claude Code's bash mode). The same sandbox guard as the `Bash` tool applies,
|
|
@@ -286,9 +293,10 @@ selected without leaving the terminal:
|
|
|
286
293
|
wheel never scrolls the input history — history is arrow keys only.
|
|
287
294
|
- `ui.mouseScroll=false` — the terminal owns the mouse again: normal text
|
|
288
295
|
selection works and the wheel is inert.
|
|
289
|
-
- `PgUp` / `PgDn`
|
|
290
|
-
|
|
291
|
-
input line is empty
|
|
296
|
+
- the mouse wheel, `PgUp` / `PgDn` (half a screen) and `Home` / `End` (the very
|
|
297
|
+
top / bottom) scroll the dialog; the wheel needs `ui.mouseScroll=true` (the default), and
|
|
298
|
+
Home/End jump only while the input line is empty (with text typed they
|
|
299
|
+
move the caret as usual).
|
|
292
300
|
- To copy a LARGE block, use `/export` (writes the whole session as Markdown)
|
|
293
301
|
or the transcript file (`/transcript` shows the path) — the terminal's own
|
|
294
302
|
drag-select cannot reach past one screen.
|
|
@@ -297,8 +305,8 @@ Set `NO_COLOR=1` to disable colors (a calm default palette is used otherwise).
|
|
|
297
305
|
|
|
298
306
|
### Images and files
|
|
299
307
|
|
|
300
|
-
You can paste an image or a file into the input line (
|
|
301
|
-
|
|
308
|
+
You can paste an image or a file into the input line (Shift+Insert or the
|
|
309
|
+
terminal's own paste; for a FILE from the clipboard press Ctrl+V). zames saves it under `<project>/tmp` and shows a
|
|
302
310
|
marker in the line - `[image#1]` for images, `[file#1]` for other files - then
|
|
303
311
|
uploads the real file to the chat together with your message. These are the
|
|
304
312
|
same formats the DeepSeek / Qwen / GLM web chat accepts (PNG, JPEG, GIF, WEBP, BMP,
|
|
@@ -368,6 +376,8 @@ The agent has the same style of tools as Claude Code / Codex CLI:
|
|
|
368
376
|
- **Git** — `GitStatus`, `GitDiff`, `GitLog`, `GitShow`, `GitBranchList`,
|
|
369
377
|
`GitAdd`, `GitCommit`, `GitPush`.
|
|
370
378
|
- **Web** — `WebFetch`, `WebSearch`.
|
|
379
|
+
- **Subagents** — `Task` (delegate a self-contained sub-task to an agent in
|
|
380
|
+
its own chat; `explore` is read-only, `general` may edit; capped per task).
|
|
371
381
|
- **Operator** — `AskUser` (a modal question with options; skipped in plan
|
|
372
382
|
mode so a plan is never interrupted).
|
|
373
383
|
- **Service** — `respond` (final answer to the operator, ends the task).
|
|
@@ -415,8 +425,9 @@ commands (/new or /clear, /sessions, /chats, /resume-id, /last, /chat, /cd,
|
|
|
415
425
|
- /review [focus] [--staged] — ask the agent to review uncommitted changes
|
|
416
426
|
and report findings (no code changes).
|
|
417
427
|
- /plan [on|off] — plan (read-only) mode. While it is on, the mutating tools
|
|
418
|
-
(Write/Edit/MultiEdit/ApplyPatch/Bash, GitAdd/GitCommit/GitPush
|
|
419
|
-
from the tool set, so
|
|
428
|
+
(Write/Edit/MultiEdit/ApplyPatch/Bash/BashOutput, GitAdd/GitCommit/GitPush,
|
|
429
|
+
AskUser) are removed from the tool set, and so are MCP tools with a mutating
|
|
430
|
+
name, so the agent can investigate without touching the tree.
|
|
420
431
|
Start in it with `--plan`.
|
|
421
432
|
- /compact — ask the active backend to compress the current chat into a handover
|
|
422
433
|
summary, then open a NEW chat, resend the system prompt and post the summary
|
|
@@ -424,9 +435,10 @@ commands (/new or /clear, /sessions, /chats, /resume-id, /last, /chat, /cd,
|
|
|
424
435
|
- /goal <condition> — set a **completion condition** (Claude Code parity). The
|
|
425
436
|
agent starts working toward it at once and keeps starting new turns until a
|
|
426
437
|
separate evaluator model judges the condition met. The goal clears itself
|
|
427
|
-
automatically when it is met (or judged impossible), or with `/goal clear
|
|
428
|
-
The condition is stored in `<project>/.zames-goal`
|
|
429
|
-
on the next launch. `/goal` with no argument
|
|
438
|
+
automatically when it is met (or judged impossible), or with `/goal clear`
|
|
439
|
+
(or its synonym `/goal off`). The condition is stored in `<project>/.zames-goal`
|
|
440
|
+
(git-ignored) and restored on the next launch. `/goal` with no argument — or
|
|
441
|
+
`/goal on` — shows the current state.
|
|
430
442
|
- /loop <interval> <task> — repeat a task periodically (e.g. `/loop 10m run the
|
|
431
443
|
tests`). /cron "<min> <hour> <dom> <month> <dow>" <task> — run on a schedule.
|
|
432
444
|
/jobs [rm <id>|clear] lists and stops them. A fired job is put into the same
|
|
@@ -440,8 +452,9 @@ tests`). /cron "<min> <hour> <dom> <month> <dow>" <task> — run on a schedule.
|
|
|
440
452
|
"Web search" item in the "+" menu.
|
|
441
453
|
These (and /queue, /jobs, /goal, `/config <sub>`) can be used WHILE the agent
|
|
442
454
|
is working — they do not touch the in-flight generation.
|
|
443
|
-
- /queue [clear] — list or clear
|
|
444
|
-
current
|
|
455
|
+
- /queue [list|ls|clear|c|clean] — list (`list`/`ls`) or clear
|
|
456
|
+
(`clear`/`c`/`clean`) the messages waiting to be sent after the current
|
|
457
|
+
task.
|
|
445
458
|
|
|
446
459
|
The token context is also shown live: the status line above the input has the
|
|
447
460
|
spinner/text on the left and the context on the right (e.g. `125k · 13%`). The
|
|
@@ -547,8 +560,7 @@ too):
|
|
|
547
560
|
|
|
548
561
|
- **Memory** (`@modelcontextprotocol/server-memory`) — a local knowledge graph
|
|
549
562
|
(9 tools) for durable facts across chats. NOTE: zames already has `MEMORY.md`
|
|
550
|
-
|
|
551
|
-
* `AGENTS.md`; add this only if you want a queryable graph.
|
|
563
|
+
+ `AGENTS.md`; add this only if you want a queryable graph.
|
|
552
564
|
|
|
553
565
|
Keep the stack SMALL: every server loads its tool definitions into the model's
|
|
554
566
|
context. Add one when you feel its absence, not before.
|
|
@@ -564,13 +576,14 @@ You can view and change settings without leaving the agent — use the
|
|
|
564
576
|
toggle in place; numbers and strings open an input prompt.
|
|
565
577
|
|
|
566
578
|
```
|
|
567
|
-
/config interactive settings menu
|
|
568
|
-
/config list print all editable settings
|
|
579
|
+
/config interactive settings menu (alias: menu)
|
|
580
|
+
/config list print all editable settings (aliases: show, ls)
|
|
569
581
|
/config get <path> show a setting
|
|
570
582
|
/config set <path> <value> change a setting
|
|
571
|
-
/config reset <path> reset a setting to its default
|
|
572
|
-
/config path show config file paths
|
|
583
|
+
/config reset <path> reset a setting to its default (alias: unset)
|
|
584
|
+
/config path show config file paths (alias: paths)
|
|
573
585
|
/config lang <ru|en> switch interface and agent language
|
|
586
|
+
(aliases: language, язык)
|
|
574
587
|
```
|
|
575
588
|
|
|
576
589
|
Examples:
|
package/llms.txt
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> zames_pro (`zames_pro` on npm, binary `zames`) is a free, open-source
|
|
4
4
|
> terminal coding agent that drives a real web chat LLM — DeepSeek
|
|
5
5
|
> (chat.deepseek.com), Qwen (chat.qwen.ai) or GLM (chat.z.ai) — through a
|
|
6
|
-
>
|
|
7
|
-
> token. It behaves like Claude Code / Codex CLI in the terminal: reads and
|
|
6
|
+
> Playwright browser (DeepSeek and Qwen headless, GLM in a visible window), so
|
|
7
|
+
> it needs NO API key and bills nothing per token. It behaves like Claude Code / Codex CLI in the terminal: reads and
|
|
8
8
|
> edits files, runs shell commands, greps the repo, and commits to git.
|
|
9
9
|
|
|
10
10
|
This file follows the llms.txt convention (https://llmstxt.org): a short,
|
|
@@ -20,6 +20,8 @@ LLM-friendly map of the project. Everything below is plain Markdown.
|
|
|
20
20
|
- **Supported backends:** DeepSeek (`chat.deepseek.com`, default), Qwen
|
|
21
21
|
(`chat.qwen.ai`), GLM (`chat.z.ai`). Live switch with
|
|
22
22
|
`/provider <deepseek|qwen|glm>`; each backend keeps its own browser profile.
|
|
23
|
+
DeepSeek and Qwen run headless; GLM runs with a VISIBLE window by default,
|
|
24
|
+
because z.ai answers every headless send with a slider captcha.
|
|
23
25
|
- **Language / stack:** TypeScript (strict), Node.js >= 22, Playwright,
|
|
24
26
|
Ink/React TUI, MCP client.
|
|
25
27
|
- **License:** MIT. **Not affiliated** with DeepSeek, Alibaba or Zhipu AI.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.82.
|
|
4
|
-
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen
|
|
3
|
+
"version": "2.82.142",
|
|
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": {
|
|
7
7
|
"zames": "dist/index.js"
|