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 +22 -0
- package/dist/agent-loop.js +5 -1
- package/dist/commands.js +201 -0
- package/dist/i18n.js +12 -0
- package/dist/index.js +159 -0
- package/package.json +1 -1
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
|
package/dist/agent-loop.js
CHANGED
|
@@ -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
|
-
|
|
321
|
+
truncHint;
|
|
318
322
|
continue;
|
|
319
323
|
}
|
|
320
324
|
const trimmed = (rawResponse || '').trim();
|
package/dist/commands.js
ADDED
|
@@ -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) {
|