zames_pro 2.14.0 → 2.15.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/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
@@ -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.0",
4
4
  "description": "Terminal coding agent over chat.deepseek.com via Playwright",
5
5
  "type": "module",
6
6
  "bin": {