zames_pro 2.14.0 → 2.15.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/README.md CHANGED
@@ -93,6 +93,28 @@ The agent has the same style of tools as Claude Code / Codex CLI:
93
93
  All file tools stay inside the working directory (sandbox). `Write`/`Edit` and
94
94
  `MultiEdit`/`ApplyPatch` make a backup (undo) before touching a file.
95
95
 
96
+ ## Slash commands
97
+
98
+ Type / in the prompt for hints (Tab completes). Besides the session and
99
+ config commands (/new, /chats, /resume, /cd, /status, /config,
100
+ /undo, /transcript, /mcp, /skills, /memory, /init, /reload,
101
+ /debug-dom, /help, /exit) there are a few that mirror Claude Code /
102
+ Codex CLI:
103
+
104
+ - /diff [--staged] — show the working-tree git diff (--staged for the index).
105
+ - /cost (alias /usage) — session stats: tasks, tool calls, duration
106
+ (DeepSeek web does not expose token counts).
107
+ - /export [file] — write the session transcript to a Markdown file
108
+ (zames-export-<stamp>.md by default).
109
+ - /doctor — diagnose node, git, config, browser, clipboard and MCP.
110
+ - /permissions — show the confirmation settings (Write/Edit/Bash + the
111
+ alwaysConfirm regex list).
112
+ - /add-dir <path> — validate an extra directory (the sandbox is fixed at
113
+ startup; relaunch with --dir to write there).
114
+ - /review [focus] [--staged] — ask the agent to review uncommitted changes
115
+ and report findings (no code changes).
116
+
117
+
96
118
  ## Project context, skills and memory
97
119
 
98
120
  Like Codex / Claude Code, zames reads project instructions and reusable
@@ -310,11 +310,15 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
310
310
  MAX_MALFORMED_RETRIES +
311
311
  ')');
312
312
  }
313
+ const large = String(rawResponse || '').length > 3000;
314
+ const truncHint = large
315
+ ? 'Your call was too long and got cut off. Do NOT resend the same huge call: split it. For a large file use Write with small content_base64 pieces, or write several smaller files. Keep each tool call under about 2000 characters.'
316
+ : 'For example: a small JSON tool-call object';
313
317
  message =
314
318
  'Your previous answer was not recognized as a tool call. ' +
315
319
  'Reply with EXACTLY one JSON tool-call object, no text before or after. ' +
316
320
  'Do NOT use XML/DSML tags — plain JSON only. ' +
317
- 'For example: {"tool": "Read", "args": {"path": "src/index.js"}}';
321
+ truncHint;
318
322
  continue;
319
323
  }
320
324
  const trimmed = (rawResponse || '').trim();
@@ -0,0 +1,201 @@
1
+ import path from 'path';
2
+ // Helpers for the extra slash commands (/diff, /cost, /export, /doctor,
3
+ // /permissions, /review, /add-dir). Pure functions, unit-tested without a
4
+ // live agent/browser. index.ts only renders their output.
5
+ const NL = String.fromCharCode(10);
6
+ const CR = String.fromCharCode(13);
7
+ // ---------- /diff ----------
8
+ export function formatDiff(diffText, opts = {}) {
9
+ const maxLines = opts.maxLines ?? 200;
10
+ const trimmed = String(diffText ?? '').split(CR + NL).join(NL).trimEnd();
11
+ if (!trimmed)
12
+ return '(no changes)';
13
+ const lines = trimmed.split(NL);
14
+ if (lines.length <= maxLines)
15
+ return trimmed;
16
+ const head = lines.slice(0, maxLines).join(NL);
17
+ const rest = lines.length - maxLines;
18
+ return (head +
19
+ NL +
20
+ '... [' + rest + ' more lines, use Bash git diff for the full output]');
21
+ }
22
+ export function diffGitArgs(staged = false) {
23
+ return staged ? 'git diff --staged' : 'git diff';
24
+ }
25
+ export function summarizeTranscript(entries) {
26
+ const stats = {
27
+ turns: 0,
28
+ toolCalls: 0,
29
+ toolCounts: {},
30
+ durationMs: 0,
31
+ startedAt: null,
32
+ };
33
+ for (const e of entries) {
34
+ if (!e || typeof e !== 'object')
35
+ continue;
36
+ if (e.type === 'user_task')
37
+ stats.turns++;
38
+ if (e.type === 'tool_call') {
39
+ stats.toolCalls++;
40
+ const tool = String(e.tool ?? '?');
41
+ stats.toolCounts[tool] = (stats.toolCounts[tool] || 0) + 1;
42
+ }
43
+ if (typeof e.elapsed === 'number' && e.elapsed > stats.durationMs) {
44
+ stats.durationMs = e.elapsed;
45
+ }
46
+ if (!stats.startedAt && typeof e.ts === 'string')
47
+ stats.startedAt = e.ts;
48
+ }
49
+ return stats;
50
+ }
51
+ export function parseTranscript(body) {
52
+ const out = [];
53
+ for (const line of String(body ?? '').split(NL)) {
54
+ const t = line.trim();
55
+ if (!t)
56
+ continue;
57
+ try {
58
+ const v = JSON.parse(t);
59
+ if (v && typeof v === 'object')
60
+ out.push(v);
61
+ }
62
+ catch {
63
+ // skip malformed lines
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ export function formatDuration(ms) {
69
+ const total = Math.max(0, Math.round(ms / 1000));
70
+ const h = Math.floor(total / 3600);
71
+ const m = Math.floor((total % 3600) / 60);
72
+ const s = total % 60;
73
+ const pad = (n) => String(n).padStart(2, '0');
74
+ if (h > 0)
75
+ return h + 'h ' + pad(m) + 'm ' + pad(s) + 's';
76
+ if (m > 0)
77
+ return m + 'm ' + pad(s) + 's';
78
+ return s + 's';
79
+ }
80
+ export function renderCost(stats, transcriptFile) {
81
+ const lines = [];
82
+ lines.push('Session stats (DeepSeek web does not expose token counts):');
83
+ lines.push(' tasks: ' + stats.turns);
84
+ lines.push(' tool calls: ' + stats.toolCalls);
85
+ const top = Object.entries(stats.toolCounts).sort((a, b) => b[1] - a[1]);
86
+ if (top.length) {
87
+ lines.push(' by tool:');
88
+ for (const [name, n] of top)
89
+ lines.push(' ' + name + ': ' + n);
90
+ }
91
+ lines.push(' duration: ' + formatDuration(stats.durationMs));
92
+ if (stats.startedAt)
93
+ lines.push(' started: ' + stats.startedAt);
94
+ lines.push(' transcript: ' + (transcriptFile || '(off)'));
95
+ return lines.join(NL);
96
+ }
97
+ // ---------- /export ----------
98
+ export function formatExport(entries, meta = {}) {
99
+ const out = [];
100
+ out.push('# zames session export');
101
+ out.push('');
102
+ if (meta.workdir)
103
+ out.push('- workdir: ' + meta.workdir);
104
+ if (meta.chatId)
105
+ out.push('- chat: ' + meta.chatId);
106
+ out.push('- exported: ' + new Date().toISOString());
107
+ out.push('');
108
+ for (const e of entries) {
109
+ const type = String(e.type ?? '?');
110
+ if (type === 'user_task') {
111
+ out.push('## Task');
112
+ out.push('');
113
+ out.push(String(e.task ?? ''));
114
+ out.push('');
115
+ }
116
+ else if (type === 'tool_call') {
117
+ out.push('- tool ' +
118
+ String(e.tool ?? '?') +
119
+ ' ' +
120
+ JSON.stringify(e.args ?? {}));
121
+ }
122
+ else if (type === 'tool_result') {
123
+ out.push(' -> ' +
124
+ String(e.tool ?? '?') +
125
+ ': ' +
126
+ String(e.result ?? '').slice(0, 500));
127
+ }
128
+ else if (type === 'assistant_final') {
129
+ out.push('## Answer');
130
+ out.push('');
131
+ out.push(String(e.message ?? ''));
132
+ out.push('');
133
+ }
134
+ }
135
+ return out.join(NL);
136
+ }
137
+ export function defaultExportPath(workdir, now = new Date()) {
138
+ const stamp = now
139
+ .toISOString()
140
+ .replace(/[:.]/g, '-')
141
+ .replace('T', '_')
142
+ .replace('Z', '');
143
+ return path.join(workdir, 'zames-export-' + stamp + '.md');
144
+ }
145
+ export function renderDoctor(d) {
146
+ const rows = [];
147
+ const row = (ok, label, value) => {
148
+ rows.push(' ' + (ok ? '[OK] ' : '[WARN]') + ' ' + label.padEnd(16) + ' ' + value);
149
+ };
150
+ row(true, 'node', d.nodeVersion);
151
+ row(true, 'platform', d.platform);
152
+ row(true, 'workdir', d.workdir);
153
+ row(d.gitOk, 'git', d.gitOk ? 'repo (' + (d.gitBranch || 'detached') + ')' : 'not a repository');
154
+ row(d.configOk, 'config', d.configOk ? 'loaded' : 'error: ' + (d.configError || 'unknown'));
155
+ row(true, 'browser', d.browserChannel ? d.browserChannel : 'bundled chromium');
156
+ row(!!d.clipboardTool, 'clipboard', d.clipboardTool || 'no tool found (image paste disabled)');
157
+ row(true, 'mcp', d.mcpServers + ' server(s), ' + d.mcpTools + ' tool(s)');
158
+ row(d.transcriptOk, 'transcript', d.transcriptOk ? 'on' : 'off');
159
+ return 'Doctor:' + NL + rows.join(NL);
160
+ }
161
+ export function renderPermissions(p) {
162
+ const onoff = (b) => (b ? 'ask' : 'allow');
163
+ const lines = [];
164
+ lines.push('Tool permissions (confirmation settings):');
165
+ lines.push(' Write: ' + onoff(p.write));
166
+ lines.push(' Edit: ' + onoff(p.edit));
167
+ lines.push(' Bash: ' + onoff(p.bash));
168
+ if (p.alwaysConfirm.length) {
169
+ lines.push(' Always confirm (regex):');
170
+ for (const re of p.alwaysConfirm)
171
+ lines.push(' ' + re);
172
+ }
173
+ lines.push('');
174
+ lines.push('Change with: /config set confirmation.write false (and .edit / .bash)');
175
+ return lines.join(NL);
176
+ }
177
+ // ---------- /add-dir ----------
178
+ export function resolveExtraDir(input, workdir) {
179
+ const raw = String(input ?? '').trim();
180
+ if (!raw)
181
+ return { error: 'Usage: /add-dir <path>' };
182
+ const abs = path.resolve(workdir, raw);
183
+ if (abs === path.resolve(workdir)) {
184
+ return { error: 'This is already the working directory.' };
185
+ }
186
+ return { path: abs };
187
+ }
188
+ // ---------- /review ----------
189
+ export function buildReviewPrompt(focus, hasStaged = false) {
190
+ const scope = hasStaged ? 'staged' : 'uncommitted';
191
+ const f = String(focus ?? '').trim();
192
+ let s = 'Review the ' +
193
+ scope +
194
+ ' changes in this repository (use GitDiff). ' +
195
+ 'Focus on real bugs: correctness, race conditions, error handling, ' +
196
+ 'security, and missing tests. Do NOT invent problems and do NOT change ' +
197
+ 'code: only report findings with file:line and a short rationale.';
198
+ if (f)
199
+ s += NL + 'Extra focus: ' + f;
200
+ return s;
201
+ }
package/dist/i18n.js CHANGED
@@ -78,6 +78,18 @@ const CATALOG = {
78
78
  'help.cmd.undo': { ru: '/undo откатить последнюю запись/правку', en: '/undo revert the last write/edit' },
79
79
  'help.cmd.undo_list': { ru: '/undo-list список того, что можно откатить', en: '/undo-list list revertable changes' },
80
80
  'help.cmd.transcript': { ru: '/transcript путь к файлу транскрипта', en: '/transcript transcript file path' },
81
+ 'help.cmd.diff': { ru: '/diff [--staged] показать git diff', en: '/diff [--staged] show working tree git diff' },
82
+ 'help.cmd.cost': { ru: '/cost статистика сессии', en: '/cost session stats' },
83
+ 'help.cmd.export': { ru: '/export [file] выгрузить сессию в Markdown', en: '/export [file] export the session to Markdown' },
84
+ 'help.cmd.doctor': { ru: '/doctor диагностика установки и конфига', en: '/doctor diagnose install and config' },
85
+ 'help.cmd.permissions': { ru: '/permissions настройки подтверждений', en: '/permissions confirmation settings' },
86
+ 'help.cmd.add_dir': { ru: '/add-dir <path> проверить директорию', en: '/add-dir <path> validate a directory' },
87
+ 'help.cmd.review': { ru: '/review [focus] ревью незакоммиченных изменений', en: '/review [focus] review uncommitted changes' },
88
+ 'diff.not_repo': { ru: 'Не git-репозиторий.', en: 'Not a git repository.' },
89
+ 'export.done': { ru: 'Сессия выгружена: {v}', en: 'Session exported: {v}' },
90
+ 'export.outside': { ru: 'Путь вне рабочей директории.', en: 'Path is outside the working directory.' },
91
+ 'adddir.not_dir': { ru: 'Нет такой директории: {v}', en: 'No such directory: {v}' },
92
+ 'adddir.note': { ru: 'Директория существует: {v}. Песочница фиксируется при запуске.', en: 'Directory exists: {v}. The sandbox is fixed at startup.' },
81
93
  'help.cmd.config': { ru: '/config настройки: показать и изменить', en: '/config settings: view and edit' },
82
94
  'help.cmd.lang': { ru: '/config lang <ru|en> сменить язык интерфейса и агента', en: '/config lang <ru|en> switch UI and agent language' },
83
95
  'help.cmd.debug_dom': { ru: '/debug-dom сохранить HTML страницы (для отладки)', en: '/debug-dom dump page HTML (debug)' },
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n
15
15
  import { Transcript } from './transcript.js';
16
16
  import { UndoStore } from './undo.js';
17
17
  import { selfReview, selfDiff, selfApply, selfList } from './self-review.js';
18
+ import { formatDiff, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, renderPermissions, resolveExtraDir, buildReviewPrompt, } from './commands.js';
18
19
  import { closeWeb } from './web.js';
19
20
  import { saveSession, loadLastSession, listSessions, sessionsDir, } from './sessions.js';
20
21
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -218,6 +219,13 @@ ${theme.bold(t('help.commands'))}
218
219
  ${t('help.cmd.undo')}
219
220
  ${t('help.cmd.undo_list')}
220
221
  ${t('help.cmd.transcript')}
222
+ ${t('help.cmd.diff')}
223
+ ${t('help.cmd.cost')}
224
+ ${t('help.cmd.export')}
225
+ ${t('help.cmd.doctor')}
226
+ ${t('help.cmd.permissions')}
227
+ ${t('help.cmd.add_dir')}
228
+ ${t('help.cmd.review')}
221
229
  ${t('help.cmd.config')}
222
230
  ${t('help.cmd.lang')}
223
231
  ${t('help.cmd.debug_dom')}
@@ -266,6 +274,13 @@ const SLASH_COMMANDS = [
266
274
  { name: '/undo', key: 'help.cmd.undo' },
267
275
  { name: '/undo-list', key: 'help.cmd.undo_list' },
268
276
  { name: '/transcript', key: 'help.cmd.transcript' },
277
+ { name: '/diff', key: 'help.cmd.diff' },
278
+ { name: '/cost', key: 'help.cmd.cost' },
279
+ { name: '/export', key: 'help.cmd.export' },
280
+ { name: '/doctor', key: 'help.cmd.doctor' },
281
+ { name: '/permissions', key: 'help.cmd.permissions' },
282
+ { name: '/add-dir', key: 'help.cmd.add_dir' },
283
+ { name: '/review', key: 'help.cmd.review' },
269
284
  { name: '/config', key: 'help.cmd.config' },
270
285
  { name: '/skills', key: 'help.cmd.skills' },
271
286
  { name: '/memory', key: 'help.cmd.memory' },
@@ -1986,6 +2001,150 @@ async function main() {
1986
2001
  console.log(theme.system(transcript.file || t('common.off')));
1987
2002
  continue;
1988
2003
  }
2004
+ if (lower === '/diff' || lower.startsWith('/diff ')) {
2005
+ const staged = lower.indexOf("--staged") !== -1;
2006
+ const { runGit } = await import('./gitTools.js');
2007
+ const probe = await runGit('git rev-parse --is-inside-work-tree', currentWorkdir, 5000);
2008
+ if (probe.trim() !== 'true') {
2009
+ console.error(theme.error(t('diff.not_repo')));
2010
+ continue;
2011
+ }
2012
+ const out = await runGit(diffGitArgs(staged), currentWorkdir, 20_000);
2013
+ console.log(theme.system(formatDiff(out, { maxLines: 400 })));
2014
+ continue;
2015
+ }
2016
+ if (lower === '/cost' || lower === '/usage') {
2017
+ let stats = summarizeTranscript([]);
2018
+ if (transcript.file) {
2019
+ try {
2020
+ const body = await fs.readFile(transcript.file, 'utf-8');
2021
+ stats = summarizeTranscript(parseTranscript(body));
2022
+ }
2023
+ catch {
2024
+ // best-effort
2025
+ }
2026
+ }
2027
+ console.log(theme.system(renderCost(stats, transcript.file)));
2028
+ continue;
2029
+ }
2030
+ if (lower === '/export' || lower.startsWith('/export ')) {
2031
+ const arg = trimmed.slice('/export'.length).trim();
2032
+ const target = arg
2033
+ ? path.resolve(currentWorkdir, arg)
2034
+ : defaultExportPath(currentWorkdir);
2035
+ const rel = path.relative(sandboxRoot, target);
2036
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
2037
+ console.error(theme.error(t('export.outside')));
2038
+ continue;
2039
+ }
2040
+ let entries = [];
2041
+ if (transcript.file) {
2042
+ try {
2043
+ entries = parseTranscript(await fs.readFile(transcript.file, 'utf-8'));
2044
+ }
2045
+ catch {
2046
+ entries = [];
2047
+ }
2048
+ }
2049
+ const md = formatExport(entries, {
2050
+ chatId: currentChatId,
2051
+ workdir: currentWorkdir,
2052
+ });
2053
+ await fs.writeFile(target, md, 'utf-8');
2054
+ console.log(theme.assistant(t('export.done', { v: target })));
2055
+ continue;
2056
+ }
2057
+ if (lower === '/doctor') {
2058
+ const { runGit } = await import('./gitTools.js');
2059
+ let gitOk = false;
2060
+ let gitBranch = null;
2061
+ try {
2062
+ const probe = await runGit('git rev-parse --is-inside-work-tree', currentWorkdir, 5000);
2063
+ gitOk = probe.trim() === 'true';
2064
+ if (gitOk) {
2065
+ gitBranch = (await runGit('git branch --show-current', currentWorkdir, 5000)).trim();
2066
+ }
2067
+ }
2068
+ catch {
2069
+ gitOk = false;
2070
+ }
2071
+ let clipboardTool = null;
2072
+ try {
2073
+ clipboardTool = hasClipboardTool() ? 'available' : null;
2074
+ }
2075
+ catch {
2076
+ clipboardTool = null;
2077
+ }
2078
+ const mcpStatus = mcpPool
2079
+ ? mcpPool.status()
2080
+ : { servers: [], toolCount: 0 };
2081
+ console.log(theme.system(renderDoctor({
2082
+ nodeVersion: process.version,
2083
+ platform: process.platform,
2084
+ workdir: currentWorkdir,
2085
+ gitOk,
2086
+ gitBranch,
2087
+ configOk: true,
2088
+ browserChannel: config.browserChannel,
2089
+ clipboardTool,
2090
+ mcpServers: mcpStatus.servers.length,
2091
+ mcpTools: mcpStatus.toolCount,
2092
+ transcriptOk: !!transcript.file,
2093
+ })));
2094
+ continue;
2095
+ }
2096
+ if (lower === '/permissions' || lower === '/allowed-tools') {
2097
+ console.log(theme.system(renderPermissions({
2098
+ write: config.confirmation.write,
2099
+ edit: config.confirmation.edit,
2100
+ bash: config.confirmation.bash,
2101
+ alwaysConfirm: config.confirmation.alwaysConfirm,
2102
+ })));
2103
+ continue;
2104
+ }
2105
+ if (lower === '/add-dir' || lower.startsWith('/add-dir ')) {
2106
+ const arg = trimmed.slice('/add-dir'.length).trim();
2107
+ const res = resolveExtraDir(arg, currentWorkdir);
2108
+ if ('error' in res) {
2109
+ console.error(theme.warn(res.error));
2110
+ continue;
2111
+ }
2112
+ const stat = await fs.stat(res.path).catch(() => null);
2113
+ if (!stat || !stat.isDirectory()) {
2114
+ console.error(theme.error(t('adddir.not_dir', { v: res.path })));
2115
+ continue;
2116
+ }
2117
+ console.log(theme.system(t('adddir.note', { v: res.path })));
2118
+ continue;
2119
+ }
2120
+ if (lower === '/review' || lower.startsWith('/review ')) {
2121
+ const rest = trimmed.slice('/review'.length).trim();
2122
+ const staged = rest.indexOf("--staged") !== -1;
2123
+ const focus = rest.replace(/--staged/g, '').trim();
2124
+ const reviewTask = buildReviewPrompt(focus, staged);
2125
+ const reviewTools = mod.createTools(currentWorkdir, { undo });
2126
+ if (editor)
2127
+ editor.busy = true;
2128
+ try {
2129
+ await runTask(browser, reviewTools, reviewTask, currentWorkdir, {
2130
+ transcript,
2131
+ freshChat: false,
2132
+ sendSystemPrompt: false,
2133
+ ui: editor || null,
2134
+ onChatReady: (chatId) => {
2135
+ if (chatId) {
2136
+ currentChatId = chatId;
2137
+ saveLastChat(chatId, currentWorkdir);
2138
+ }
2139
+ },
2140
+ });
2141
+ }
2142
+ finally {
2143
+ if (editor)
2144
+ editor.busy = false;
2145
+ }
2146
+ continue;
2147
+ }
1989
2148
  if (lower === '/undo') {
1990
2149
  const result = await undo.undoLast();
1991
2150
  if (result.ok && result.record) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.14.0",
3
+ "version": "2.15.1",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {