@serjm/deepseek-code 0.3.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.
Files changed (216) hide show
  1. package/CONTRIBUTING.md +73 -0
  2. package/README.md +194 -0
  3. package/README.ru.md +194 -0
  4. package/dist/api/index.d.ts +77 -0
  5. package/dist/api/index.d.ts.map +1 -0
  6. package/dist/api/index.js +263 -0
  7. package/dist/api/index.js.map +1 -0
  8. package/dist/cli/headless.d.ts +22 -0
  9. package/dist/cli/headless.d.ts.map +1 -0
  10. package/dist/cli/headless.js +122 -0
  11. package/dist/cli/headless.js.map +1 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +90 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/cli/interactive.d.ts +18 -0
  17. package/dist/cli/interactive.d.ts.map +1 -0
  18. package/dist/cli/interactive.js +75 -0
  19. package/dist/cli/interactive.js.map +1 -0
  20. package/dist/commands/index.d.ts +30 -0
  21. package/dist/commands/index.d.ts.map +1 -0
  22. package/dist/commands/index.js +964 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/config/defaults.d.ts +37 -0
  25. package/dist/config/defaults.d.ts.map +1 -0
  26. package/dist/config/defaults.js +39 -0
  27. package/dist/config/defaults.js.map +1 -0
  28. package/dist/config/loader.d.ts +4 -0
  29. package/dist/config/loader.d.ts.map +1 -0
  30. package/dist/config/loader.js +76 -0
  31. package/dist/config/loader.js.map +1 -0
  32. package/dist/core/agent-loop.d.ts +111 -0
  33. package/dist/core/agent-loop.d.ts.map +1 -0
  34. package/dist/core/agent-loop.js +485 -0
  35. package/dist/core/agent-loop.js.map +1 -0
  36. package/dist/core/checkpoint.d.ts +10 -0
  37. package/dist/core/checkpoint.d.ts.map +1 -0
  38. package/dist/core/checkpoint.js +83 -0
  39. package/dist/core/checkpoint.js.map +1 -0
  40. package/dist/core/extensions.d.ts +55 -0
  41. package/dist/core/extensions.d.ts.map +1 -0
  42. package/dist/core/extensions.js +113 -0
  43. package/dist/core/extensions.js.map +1 -0
  44. package/dist/core/git.d.ts +68 -0
  45. package/dist/core/git.d.ts.map +1 -0
  46. package/dist/core/git.js +148 -0
  47. package/dist/core/git.js.map +1 -0
  48. package/dist/core/hooks.d.ts +37 -0
  49. package/dist/core/hooks.d.ts.map +1 -0
  50. package/dist/core/hooks.js +77 -0
  51. package/dist/core/hooks.js.map +1 -0
  52. package/dist/core/i18n.d.ts +90 -0
  53. package/dist/core/i18n.d.ts.map +1 -0
  54. package/dist/core/i18n.js +253 -0
  55. package/dist/core/i18n.js.map +1 -0
  56. package/dist/core/lsp.d.ts +74 -0
  57. package/dist/core/lsp.d.ts.map +1 -0
  58. package/dist/core/lsp.js +239 -0
  59. package/dist/core/lsp.js.map +1 -0
  60. package/dist/core/mcp.d.ts +49 -0
  61. package/dist/core/mcp.d.ts.map +1 -0
  62. package/dist/core/mcp.js +195 -0
  63. package/dist/core/mcp.js.map +1 -0
  64. package/dist/core/memory.d.ts +38 -0
  65. package/dist/core/memory.d.ts.map +1 -0
  66. package/dist/core/memory.js +231 -0
  67. package/dist/core/memory.js.map +1 -0
  68. package/dist/core/metrics.d.ts +36 -0
  69. package/dist/core/metrics.d.ts.map +1 -0
  70. package/dist/core/metrics.js +111 -0
  71. package/dist/core/metrics.js.map +1 -0
  72. package/dist/core/review.d.ts +27 -0
  73. package/dist/core/review.d.ts.map +1 -0
  74. package/dist/core/review.js +201 -0
  75. package/dist/core/review.js.map +1 -0
  76. package/dist/core/sandbox.d.ts +52 -0
  77. package/dist/core/sandbox.d.ts.map +1 -0
  78. package/dist/core/sandbox.js +140 -0
  79. package/dist/core/sandbox.js.map +1 -0
  80. package/dist/core/scheduler.d.ts +56 -0
  81. package/dist/core/scheduler.d.ts.map +1 -0
  82. package/dist/core/scheduler.js +167 -0
  83. package/dist/core/scheduler.js.map +1 -0
  84. package/dist/core/session.d.ts +49 -0
  85. package/dist/core/session.d.ts.map +1 -0
  86. package/dist/core/session.js +127 -0
  87. package/dist/core/session.js.map +1 -0
  88. package/dist/core/skills.d.ts +36 -0
  89. package/dist/core/skills.d.ts.map +1 -0
  90. package/dist/core/skills.js +90 -0
  91. package/dist/core/skills.js.map +1 -0
  92. package/dist/core/subagent.d.ts +45 -0
  93. package/dist/core/subagent.d.ts.map +1 -0
  94. package/dist/core/subagent.js +130 -0
  95. package/dist/core/subagent.js.map +1 -0
  96. package/dist/core/themes.d.ts +35 -0
  97. package/dist/core/themes.d.ts.map +1 -0
  98. package/dist/core/themes.js +188 -0
  99. package/dist/core/themes.js.map +1 -0
  100. package/dist/tools/bash.d.ts +3 -0
  101. package/dist/tools/bash.d.ts.map +1 -0
  102. package/dist/tools/bash.js +92 -0
  103. package/dist/tools/bash.js.map +1 -0
  104. package/dist/tools/chrome-manager.d.ts +35 -0
  105. package/dist/tools/chrome-manager.d.ts.map +1 -0
  106. package/dist/tools/chrome-manager.js +163 -0
  107. package/dist/tools/chrome-manager.js.map +1 -0
  108. package/dist/tools/chrome.d.ts +78 -0
  109. package/dist/tools/chrome.d.ts.map +1 -0
  110. package/dist/tools/chrome.js +1058 -0
  111. package/dist/tools/chrome.js.map +1 -0
  112. package/dist/tools/edit.d.ts +3 -0
  113. package/dist/tools/edit.d.ts.map +1 -0
  114. package/dist/tools/edit.js +81 -0
  115. package/dist/tools/edit.js.map +1 -0
  116. package/dist/tools/glob.d.ts +3 -0
  117. package/dist/tools/glob.d.ts.map +1 -0
  118. package/dist/tools/glob.js +41 -0
  119. package/dist/tools/glob.js.map +1 -0
  120. package/dist/tools/grep.d.ts +3 -0
  121. package/dist/tools/grep.d.ts.map +1 -0
  122. package/dist/tools/grep.js +74 -0
  123. package/dist/tools/grep.js.map +1 -0
  124. package/dist/tools/path-safety.d.ts +3 -0
  125. package/dist/tools/path-safety.d.ts.map +1 -0
  126. package/dist/tools/path-safety.js +19 -0
  127. package/dist/tools/path-safety.js.map +1 -0
  128. package/dist/tools/read.d.ts +3 -0
  129. package/dist/tools/read.d.ts.map +1 -0
  130. package/dist/tools/read.js +58 -0
  131. package/dist/tools/read.js.map +1 -0
  132. package/dist/tools/registry.d.ts +4 -0
  133. package/dist/tools/registry.d.ts.map +1 -0
  134. package/dist/tools/registry.js +43 -0
  135. package/dist/tools/registry.js.map +1 -0
  136. package/dist/tools/types.d.ts +47 -0
  137. package/dist/tools/types.d.ts.map +1 -0
  138. package/dist/tools/types.js +90 -0
  139. package/dist/tools/types.js.map +1 -0
  140. package/dist/tools/write.d.ts +3 -0
  141. package/dist/tools/write.d.ts.map +1 -0
  142. package/dist/tools/write.js +51 -0
  143. package/dist/tools/write.js.map +1 -0
  144. package/dist/ui/activity-cards.d.ts +50 -0
  145. package/dist/ui/activity-cards.d.ts.map +1 -0
  146. package/dist/ui/activity-cards.js +185 -0
  147. package/dist/ui/activity-cards.js.map +1 -0
  148. package/dist/ui/app.d.ts +9 -0
  149. package/dist/ui/app.d.ts.map +1 -0
  150. package/dist/ui/app.js +852 -0
  151. package/dist/ui/app.js.map +1 -0
  152. package/dist/ui/chat-view.d.ts +10 -0
  153. package/dist/ui/chat-view.d.ts.map +1 -0
  154. package/dist/ui/chat-view.js +94 -0
  155. package/dist/ui/chat-view.js.map +1 -0
  156. package/dist/ui/error-boundary.d.ts +13 -0
  157. package/dist/ui/error-boundary.d.ts.map +1 -0
  158. package/dist/ui/error-boundary.js +16 -0
  159. package/dist/ui/error-boundary.js.map +1 -0
  160. package/dist/ui/fade-in.d.ts +8 -0
  161. package/dist/ui/fade-in.d.ts.map +1 -0
  162. package/dist/ui/fade-in.js +14 -0
  163. package/dist/ui/fade-in.js.map +1 -0
  164. package/dist/ui/input-bar.d.ts +16 -0
  165. package/dist/ui/input-bar.d.ts.map +1 -0
  166. package/dist/ui/input-bar.js +269 -0
  167. package/dist/ui/input-bar.js.map +1 -0
  168. package/dist/ui/markdown-view.d.ts +9 -0
  169. package/dist/ui/markdown-view.d.ts.map +1 -0
  170. package/dist/ui/markdown-view.js +240 -0
  171. package/dist/ui/markdown-view.js.map +1 -0
  172. package/dist/ui/matrix-rain.d.ts +2 -0
  173. package/dist/ui/matrix-rain.d.ts.map +1 -0
  174. package/dist/ui/matrix-rain.js +134 -0
  175. package/dist/ui/matrix-rain.js.map +1 -0
  176. package/dist/ui/reasoning-view.d.ts +12 -0
  177. package/dist/ui/reasoning-view.d.ts.map +1 -0
  178. package/dist/ui/reasoning-view.js +34 -0
  179. package/dist/ui/reasoning-view.js.map +1 -0
  180. package/dist/ui/results-panel.d.ts +11 -0
  181. package/dist/ui/results-panel.d.ts.map +1 -0
  182. package/dist/ui/results-panel.js +17 -0
  183. package/dist/ui/results-panel.js.map +1 -0
  184. package/dist/ui/setup-wizard.d.ts +30 -0
  185. package/dist/ui/setup-wizard.d.ts.map +1 -0
  186. package/dist/ui/setup-wizard.js +166 -0
  187. package/dist/ui/setup-wizard.js.map +1 -0
  188. package/dist/ui/status-bar.d.ts +14 -0
  189. package/dist/ui/status-bar.d.ts.map +1 -0
  190. package/dist/ui/status-bar.js +63 -0
  191. package/dist/ui/status-bar.js.map +1 -0
  192. package/dist/ui/tool-activity-card.d.ts +9 -0
  193. package/dist/ui/tool-activity-card.d.ts.map +1 -0
  194. package/dist/ui/tool-activity-card.js +172 -0
  195. package/dist/ui/tool-activity-card.js.map +1 -0
  196. package/dist/ui/tool-call-view.d.ts +9 -0
  197. package/dist/ui/tool-call-view.d.ts.map +1 -0
  198. package/dist/ui/tool-call-view.js +149 -0
  199. package/dist/ui/tool-call-view.js.map +1 -0
  200. package/dist/utils/clipboard.d.ts +6 -0
  201. package/dist/utils/clipboard.d.ts.map +1 -0
  202. package/dist/utils/clipboard.js +56 -0
  203. package/dist/utils/clipboard.js.map +1 -0
  204. package/dist/utils/ignore.d.ts +6 -0
  205. package/dist/utils/ignore.d.ts.map +1 -0
  206. package/dist/utils/ignore.js +40 -0
  207. package/dist/utils/ignore.js.map +1 -0
  208. package/dist/utils/logger.d.ts +4 -0
  209. package/dist/utils/logger.d.ts.map +1 -0
  210. package/dist/utils/logger.js +13 -0
  211. package/dist/utils/logger.js.map +1 -0
  212. package/dist/utils/string-width.d.ts +6 -0
  213. package/dist/utils/string-width.d.ts.map +1 -0
  214. package/dist/utils/string-width.js +37 -0
  215. package/dist/utils/string-width.js.map +1 -0
  216. package/package.json +68 -0
@@ -0,0 +1,964 @@
1
+ import { platform } from 'node:os';
2
+ import { DEEPSEEK_MODELS } from '../config/defaults.js';
3
+ import { saveMemory, listMemories, deleteMemory, searchMemories } from '../core/memory.js';
4
+ import { createCheckpoint, listCheckpoints, restoreCheckpoint } from '../core/checkpoint.js';
5
+ import { mcpManager } from '../core/mcp.js';
6
+ import { subAgentManager } from '../core/subagent.js';
7
+ import { skillsManager } from '../core/skills.js';
8
+ import { reviewCode, formatReviewReport } from '../core/review.js';
9
+ import { sandbox } from '../core/sandbox.js';
10
+ import { getSandboxUnsupportedCard } from '../ui/activity-cards.js';
11
+ import { gitIntegration } from '../core/git.js';
12
+ import { scheduler, Scheduler } from '../core/scheduler.js';
13
+ import { themeManager } from '../core/themes.js';
14
+ import { i18n } from '../core/i18n.js';
15
+ import { extensionManager } from '../core/extensions.js';
16
+ import { DeepSeekAPI } from '../api/index.js';
17
+ import { saveConfig } from '../config/loader.js';
18
+ import { getDefaultTools, getToolsForMode } from '../tools/registry.js';
19
+ import { browserTest, getLastBrowserTestResult, browserRealTest } from '../tools/chrome.js';
20
+ import { chromeManager } from '../tools/chrome-manager.js';
21
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
22
+ function generateFollowups(lastContent) {
23
+ const suggestions = [];
24
+ if (/error|Error|failed/.test(lastContent)) {
25
+ suggestions.push('Fix the error and try again');
26
+ suggestions.push('Show me the full error trace');
27
+ suggestions.push('Debug this issue step by step');
28
+ }
29
+ if (/```|code/.test(lastContent)) {
30
+ suggestions.push('Explain this code in detail');
31
+ suggestions.push('Add tests for this code');
32
+ suggestions.push('Optimize this code');
33
+ }
34
+ if (/review|Review/.test(lastContent)) {
35
+ suggestions.push('Apply the suggested fixes');
36
+ suggestions.push('Run a deeper review');
37
+ suggestions.push('Check for security issues');
38
+ }
39
+ if (suggestions.length === 0) {
40
+ suggestions.push('Continue with the next step');
41
+ suggestions.push('Explain what was done');
42
+ suggestions.push('Show me alternative approaches');
43
+ }
44
+ return suggestions;
45
+ }
46
+ // ─── Command handlers ────────────────────────────────────────────────────────
47
+ async function cmdHelp(ctx) {
48
+ const lines = ['**Available commands:**', ''];
49
+ for (const cmd of COMMANDS) {
50
+ if (cmd.name === '/language')
51
+ continue;
52
+ lines.push(` ${cmd.name.padEnd(22)} ${cmd.description}`);
53
+ }
54
+ lines.push('', ' /clear Clear chat history (with confirmation)');
55
+ lines.push('', '**Keyboard shortcuts:**', '');
56
+ lines.push(' Ctrl+L Clear chat (opens confirmation dialog)');
57
+ lines.push(' Ctrl+C Cancel running agent / double-tap to exit');
58
+ lines.push(' Alt+V Paste image from clipboard (vision models only)');
59
+ if (platform() === 'win32') {
60
+ lines.push(' **Windows note:** If Alt+V does not work, ensure your terminal sends');
61
+ lines.push(' proper Alt/Meta sequences. Windows Terminal ≥ 1.14 works correctly.');
62
+ lines.push(' In older terminals try: Settings → Compatibility → "Use Alt as Meta key".');
63
+ }
64
+ lines.push(' Tab Cycle approval mode: plan → default → auto-edit → turbo');
65
+ lines.push(' PageUp / PageDown Scroll chat history');
66
+ lines.push(' End Jump to latest message');
67
+ lines.push(' Shift+Enter Insert newline in input');
68
+ ctx.setMessages(prev => [...prev, { role: 'assistant', content: lines.join('\n') }]);
69
+ return true;
70
+ }
71
+ async function cmdSetup(ctx) {
72
+ ctx.setSetupStep('lang');
73
+ ctx.setMessages([]);
74
+ return true;
75
+ }
76
+ async function cmdRemember(ctx, input) {
77
+ const text = input.slice('/remember'.length).trim();
78
+ if (!text) {
79
+ ctx.setMessages(prev => [...prev, {
80
+ role: 'assistant',
81
+ content: 'Usage: /remember <text> — save something to memory',
82
+ }]);
83
+ return true;
84
+ }
85
+ await saveMemory({
86
+ name: `note_${Date.now()}`,
87
+ description: text.slice(0, 80),
88
+ type: 'reference',
89
+ content: text,
90
+ });
91
+ ctx.setMessages(prev => [...prev, {
92
+ role: 'assistant',
93
+ content: `✓ Saved to memory: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`,
94
+ }]);
95
+ return true;
96
+ }
97
+ async function cmdForget(ctx, input) {
98
+ const query = input.slice('/forget'.length).trim();
99
+ if (!query) {
100
+ ctx.setMessages(prev => [...prev, {
101
+ role: 'assistant',
102
+ content: 'Usage: /forget <query>',
103
+ }]);
104
+ return true;
105
+ }
106
+ const results = await searchMemories(query);
107
+ if (results.length === 0) {
108
+ ctx.setMessages(prev => [...prev, {
109
+ role: 'assistant',
110
+ content: 'No matching memories found.',
111
+ }]);
112
+ return true;
113
+ }
114
+ for (const mem of results) {
115
+ await deleteMemory(mem.name);
116
+ }
117
+ ctx.setMessages(prev => [...prev, {
118
+ role: 'assistant',
119
+ content: `✓ Deleted ${results.length} memory/memories matching: "${query}"`,
120
+ }]);
121
+ return true;
122
+ }
123
+ async function cmdMemory(ctx) {
124
+ const memories = await listMemories();
125
+ if (memories.length === 0) {
126
+ ctx.setMessages(prev => [...prev, {
127
+ role: 'assistant',
128
+ content: 'No memories saved yet. Use /remember <text> to save something.',
129
+ }]);
130
+ return true;
131
+ }
132
+ const memoryList = memories.map((m, i) => `${i + 1}. **${m.name}** — ${m.description}`).join('\n');
133
+ ctx.setMessages(prev => [...prev, {
134
+ role: 'assistant',
135
+ content: `📝 **Memories** (${memories.length}):\n\n${memoryList}`,
136
+ }]);
137
+ return true;
138
+ }
139
+ async function cmdCompress(ctx) {
140
+ const totalLen = ctx.messages.reduce((sum, m) => {
141
+ const content = typeof m.content === 'string' ? m.content : '';
142
+ return sum + content.length;
143
+ }, 0);
144
+ const msgCount = ctx.messages.length;
145
+ if (msgCount > 4 && totalLen > 2000) {
146
+ ctx.setStatusText('Compressing...');
147
+ try {
148
+ const api = new DeepSeekAPI({ ...ctx.config, apiKey: ctx.config.apiKey });
149
+ const result = await api.chat([
150
+ { role: 'system', content: 'Summarize the following conversation concisely. Keep all technical details, errors, decisions, and action items. Output in bullet points.' },
151
+ ...ctx.messages.slice(-10).filter(m => m.role !== 'system').map(m => ({
152
+ role: m.role,
153
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
154
+ })),
155
+ ]);
156
+ const summary = result.content || 'Summary unavailable.';
157
+ const systemMsg = ctx.messages.find(m => m.role === 'system');
158
+ ctx.setMessages([
159
+ ...(systemMsg ? [systemMsg] : []),
160
+ { role: 'assistant', content: `📦 **Context Compressed**\n\nOriginal: ${msgCount} messages (~${(totalLen / 1024).toFixed(1)}KB)\n\n**Summary:**\n${summary}` },
161
+ ]);
162
+ }
163
+ catch {
164
+ ctx.setMessages(prev => [...prev, {
165
+ role: 'assistant',
166
+ content: `Context compression failed. Current size: ~${(totalLen / 1024).toFixed(1)}KB across ${msgCount} messages.`,
167
+ }]);
168
+ }
169
+ }
170
+ else {
171
+ ctx.setMessages(prev => [...prev, {
172
+ role: 'assistant',
173
+ content: `Context is small (~${(totalLen / 1024).toFixed(1)}KB, ${msgCount} messages). No compression needed.`,
174
+ }]);
175
+ }
176
+ ctx.setStatusText('Ready');
177
+ return true;
178
+ }
179
+ async function cmdCheckpoint(ctx, input) {
180
+ const cpMsg = input.slice('/checkpoint'.length).trim() || `Checkpoint at ${new Date().toLocaleTimeString()}`;
181
+ const cp = await createCheckpoint(cpMsg);
182
+ if (!cp) {
183
+ ctx.setMessages(prev => [...prev, {
184
+ role: 'assistant',
185
+ content: 'Checkpoint requires a git repository.',
186
+ }]);
187
+ return true;
188
+ }
189
+ ctx.setMessages(prev => [...prev, {
190
+ role: 'assistant',
191
+ content: `✓ Checkpoint created: **${cp.id}**\nFiles: ${cp.files.length > 0 ? cp.files.join(', ') : '(no changes)'}`,
192
+ }]);
193
+ return true;
194
+ }
195
+ async function cmdRestore(ctx, input) {
196
+ const arg = input.slice('/restore'.length).trim();
197
+ if (!arg) {
198
+ const checkpoints = await listCheckpoints();
199
+ if (checkpoints.length === 0) {
200
+ ctx.setMessages(prev => [...prev, {
201
+ role: 'assistant',
202
+ content: 'No checkpoints found.',
203
+ }]);
204
+ return true;
205
+ }
206
+ const list = checkpoints.slice(0, 10).map((cp, i) => `${i + 1}. **${cp.id}** — ${cp.message} (${new Date(cp.timestamp).toLocaleString()})`).join('\n');
207
+ ctx.setMessages(prev => [...prev, {
208
+ role: 'assistant',
209
+ content: `📋 **Checkpoints:**\n${list}\n\nUse \`/restore <id>\` to restore.`,
210
+ }]);
211
+ return true;
212
+ }
213
+ ctx.setStatusText('Restoring checkpoint...');
214
+ try {
215
+ const restoredMessages = await restoreCheckpoint(arg);
216
+ ctx.setMessages(prev => [...prev, {
217
+ role: 'assistant',
218
+ content: restoredMessages
219
+ ? `✓ Restored checkpoint: ${arg}`
220
+ : `✗ Could not restore checkpoint: ${arg}`,
221
+ }]);
222
+ }
223
+ catch (err) {
224
+ ctx.setMessages(prev => [...prev, {
225
+ role: 'assistant',
226
+ content: `❌ Restore failed: ${err.message}`,
227
+ }]);
228
+ }
229
+ ctx.setStatusText('Ready');
230
+ return true;
231
+ }
232
+ async function cmdMcp(ctx, input) {
233
+ const sub = input.slice('/mcp'.length).trim();
234
+ if (sub === 'list' || !sub) {
235
+ const tools = mcpManager.getAllTools();
236
+ if (tools.length === 0) {
237
+ ctx.setMessages(prev => [...prev, {
238
+ role: 'assistant',
239
+ content: 'No MCP tools available. Configure servers in `.deepseek-code/mcp.json`.',
240
+ }]);
241
+ }
242
+ else {
243
+ const list = tools.map(t => `- **${t.serverName}/${t.name}**: ${t.description}`).join('\n');
244
+ ctx.setMessages(prev => [...prev, {
245
+ role: 'assistant',
246
+ content: `**MCP Tools (${tools.length}):**\n${list}`,
247
+ }]);
248
+ }
249
+ }
250
+ else if (sub.startsWith('connect ')) {
251
+ const name = sub.slice('connect '.length).trim();
252
+ const server = mcpManager.getServer(name);
253
+ if (!server) {
254
+ ctx.setMessages(prev => [...prev, {
255
+ role: 'assistant',
256
+ content: `Server "${name}" not found.`,
257
+ }]);
258
+ return true;
259
+ }
260
+ try {
261
+ await server.connect();
262
+ ctx.setMessages(prev => [...prev, {
263
+ role: 'assistant',
264
+ content: `✓ Connected to MCP server: ${name} (${server.tools.length} tools)`,
265
+ }]);
266
+ }
267
+ catch (err) {
268
+ ctx.setMessages(prev => [...prev, {
269
+ role: 'assistant',
270
+ content: `❌ MCP connection failed: ${err.message}`,
271
+ }]);
272
+ }
273
+ }
274
+ else {
275
+ ctx.setMessages(prev => [...prev, {
276
+ role: 'assistant',
277
+ content: 'Usage: `/mcp list` or `/mcp connect <server-name>`',
278
+ }]);
279
+ }
280
+ return true;
281
+ }
282
+ async function cmdSkills(ctx, input) {
283
+ const name = input.slice('/skills'.length).trim();
284
+ if (name) {
285
+ const skill = skillsManager.getSkill(name);
286
+ if (!skill) {
287
+ ctx.setMessages(prev => [...prev, {
288
+ role: 'assistant',
289
+ content: `Skill "${name}" not found.`,
290
+ }]);
291
+ return true;
292
+ }
293
+ ctx.setMessages(prev => [...prev, {
294
+ role: 'assistant',
295
+ content: `📖 **Skill: ${skill.name}**\n\n${skill.description || ''}`,
296
+ }]);
297
+ }
298
+ else {
299
+ const skills = skillsManager.listSkills();
300
+ const list = skills.length === 0
301
+ ? 'No skills available. Create one in `.deepseek-code/skills/<name>/SKILL.md`.'
302
+ : skills.map(s => `- **${s.name}**: ${s.description}`).join('\n');
303
+ ctx.setMessages(prev => [...prev, {
304
+ role: 'assistant',
305
+ content: `📖 **Available Skills**\n\n${list}`,
306
+ }]);
307
+ }
308
+ return true;
309
+ }
310
+ async function cmdAgents(ctx) {
311
+ const allAgents = subAgentManager['agents'];
312
+ if (allAgents.size === 0) {
313
+ ctx.setMessages(prev => [...prev, {
314
+ role: 'assistant',
315
+ content: 'No subagents configured. Create them in `.deepseek-code/agents/`.',
316
+ }]);
317
+ }
318
+ else {
319
+ const list = Array.from(allAgents.keys()).map(name => `- **${name}**`).join('\n');
320
+ ctx.setMessages(prev => [...prev, {
321
+ role: 'assistant',
322
+ content: `**Registered Subagents:**\n${list}`,
323
+ }]);
324
+ }
325
+ return true;
326
+ }
327
+ async function cmdReview(ctx, input) {
328
+ const sub = input.slice('/review'.length).trim();
329
+ const options = {};
330
+ if (sub === 'all') {
331
+ options.files = ['src'];
332
+ }
333
+ else if (sub.startsWith('diff')) {
334
+ options.gitRef = sub.slice('diff'.length).trim() || 'HEAD';
335
+ }
336
+ else if (sub === 'auto') {
337
+ options.autoFix = true;
338
+ }
339
+ ctx.setStatusText('Reviewing code...');
340
+ ctx.setMessages(prev => [...prev, { role: 'assistant', content: '🔍 Running code review...' }]);
341
+ try {
342
+ const result = await reviewCode(ctx.config, options);
343
+ const report = formatReviewReport(result);
344
+ ctx.setMessages(prev => [...prev, {
345
+ role: 'assistant',
346
+ content: `**Code Review Results**\n\nScore: **${result.score}/100**\nIssues: ${result.issues.length}\nDuration: ${(result.durationMs / 1000).toFixed(1)}s\n\n${report || '✅ No issues found.'}\n\n${result.summary}`,
347
+ }]);
348
+ }
349
+ catch (err) {
350
+ ctx.setMessages(prev => [...prev, {
351
+ role: 'assistant',
352
+ content: `Review failed: ${err.message}`,
353
+ }]);
354
+ }
355
+ ctx.setStatusText('Ready');
356
+ return true;
357
+ }
358
+ async function cmdSandbox(ctx, input) {
359
+ const cmd = input.slice('/sandbox'.length).trim();
360
+ if (!cmd) {
361
+ ctx.setMessages(prev => [...prev, {
362
+ role: 'assistant',
363
+ content: 'Usage: /sandbox <command> — run command in isolated sandbox',
364
+ }]);
365
+ return true;
366
+ }
367
+ // Check platform capability
368
+ const cap = sandbox.getCapabilityInfo();
369
+ if (!cap.supported) {
370
+ getSandboxUnsupportedCard();
371
+ ctx.setMessages(prev => [...prev, {
372
+ role: 'tool',
373
+ content: JSON.stringify({
374
+ type: 'tool_activity_card',
375
+ toolCalls: [{
376
+ id: 'sandbox-check',
377
+ name: 'run_shell_command',
378
+ arguments: { command: cmd },
379
+ status: 'failed',
380
+ error: cap.reason,
381
+ }],
382
+ status: 'live'
383
+ }),
384
+ }]);
385
+ return true;
386
+ }
387
+ ctx.setStatusText('Running in sandbox...');
388
+ try {
389
+ const result = await sandbox.execute(cmd, { timeout: 60000 });
390
+ ctx.setMessages(prev => [...prev, {
391
+ role: 'assistant',
392
+ content: result.exitCode === 0
393
+ ? `✅ **Sandbox Result** (${(result.durationMs / 1000).toFixed(1)}s, exit: ${result.exitCode})\n\n${result.stdout.slice(0, 2000)}${result.stderr ? `\n\n**Stderr:**\n${result.stderr.slice(0, 1000)}` : ''}`
394
+ : `❌ **Sandbox Error** (exit: ${result.exitCode})\n\n${result.stderr.slice(0, 2000)}`,
395
+ }]);
396
+ }
397
+ catch (err) {
398
+ ctx.setMessages(prev => [...prev, {
399
+ role: 'assistant',
400
+ content: `Sandbox error: ${err.message}`,
401
+ }]);
402
+ }
403
+ ctx.setStatusText('Ready');
404
+ return true;
405
+ }
406
+ async function cmdGit(ctx, input) {
407
+ const parts = input.slice('/git'.length).trim().split(/\s+/);
408
+ const sub = parts[0];
409
+ const args = parts.slice(1).join(' ');
410
+ switch (sub) {
411
+ case 'commit':
412
+ case undefined: {
413
+ const msg = args || 'Update';
414
+ ctx.setStatusText('Committing...');
415
+ try {
416
+ const result = await gitIntegration.commit({ message: msg, all: true });
417
+ ctx.setMessages(prev => [...prev, {
418
+ role: 'assistant',
419
+ content: result.success
420
+ ? `✓ Committed: \`${msg}\` (${result.hash?.slice(0, 7)})`
421
+ : `✗ Commit failed: ${result.error}`,
422
+ }]);
423
+ }
424
+ catch (err) {
425
+ ctx.setMessages(prev => [...prev, {
426
+ role: 'assistant',
427
+ content: `❌ Commit failed: ${err.message}`,
428
+ }]);
429
+ }
430
+ ctx.setStatusText('Ready');
431
+ return true;
432
+ }
433
+ case 'branch': {
434
+ const name = args;
435
+ if (!name) {
436
+ ctx.setMessages(prev => [...prev, {
437
+ role: 'assistant',
438
+ content: 'Usage: /git branch <name>',
439
+ }]);
440
+ return true;
441
+ }
442
+ try {
443
+ const result = await gitIntegration.createBranch({ name });
444
+ ctx.setMessages(prev => [...prev, {
445
+ role: 'assistant',
446
+ content: result.success
447
+ ? `🌿 Switched to branch: ${name}`
448
+ : `✗ ${result.error}`,
449
+ }]);
450
+ }
451
+ catch (err) {
452
+ ctx.setMessages(prev => [...prev, {
453
+ role: 'assistant',
454
+ content: `❌ Branch failed: ${err.message}`,
455
+ }]);
456
+ }
457
+ return true;
458
+ }
459
+ case 'diff': {
460
+ try {
461
+ const diff = await gitIntegration.getDiff(parts[1] || undefined);
462
+ ctx.setMessages(prev => [...prev, {
463
+ role: 'assistant',
464
+ content: diff ? `\`\`\`diff\n${diff.slice(0, 3000)}\n\`\`\`` : 'No changes to show.',
465
+ }]);
466
+ }
467
+ catch (err) {
468
+ ctx.setMessages(prev => [...prev, {
469
+ role: 'assistant',
470
+ content: `❌ Diff failed: ${err.message}`,
471
+ }]);
472
+ }
473
+ return true;
474
+ }
475
+ case 'status': {
476
+ try {
477
+ const { execSync } = await import('node:child_process');
478
+ const status = execSync('git status', { encoding: 'utf-8', windowsHide: true });
479
+ ctx.setMessages(prev => [...prev, {
480
+ role: 'assistant',
481
+ content: `\`\`\`\n${status}\n\`\`\``,
482
+ }]);
483
+ }
484
+ catch (err) {
485
+ ctx.setMessages(prev => [...prev, {
486
+ role: 'assistant',
487
+ content: `❌ Status failed: ${err.message}`,
488
+ }]);
489
+ }
490
+ return true;
491
+ }
492
+ default:
493
+ ctx.setMessages(prev => [...prev, {
494
+ role: 'assistant',
495
+ content: 'Usage: `/git commit <msg>`, `/git branch <name>`, `/git diff`, `/git status`',
496
+ }]);
497
+ return true;
498
+ }
499
+ }
500
+ async function cmdLoop(ctx, input) {
501
+ const sub = input.slice('/loop'.length).trim();
502
+ if (sub === 'list') {
503
+ const tasks = scheduler.listTasks();
504
+ if (tasks.length === 0) {
505
+ ctx.setMessages(prev => [...prev, {
506
+ role: 'assistant',
507
+ content: 'No active tasks.',
508
+ }]);
509
+ }
510
+ else {
511
+ const list = tasks.map((t, i) => `${i + 1}. **${t.prompt.slice(0, 40)}** — every ${(t.interval / 1000).toFixed(0)}s (${t.runCount}/${t.maxRuns ?? '∞'})`).join('\n');
512
+ ctx.setMessages(prev => [...prev, {
513
+ role: 'assistant',
514
+ content: `**Active Tasks:**\n${list}`,
515
+ }]);
516
+ }
517
+ }
518
+ else if (sub === 'clear') {
519
+ scheduler.clearAll();
520
+ ctx.setMessages(prev => [...prev, {
521
+ role: 'assistant',
522
+ content: '✓ All tasks cleared.',
523
+ }]);
524
+ }
525
+ else if (sub) {
526
+ const parts = sub.split(/\s+/);
527
+ const intervalStr = parts[0];
528
+ const prompt = parts.slice(1).join(' ') || 'check status';
529
+ const intervalMs = Scheduler.parseInterval(intervalStr);
530
+ if (!intervalMs) {
531
+ ctx.setMessages(prev => [...prev, {
532
+ role: 'assistant',
533
+ content: `Invalid interval: "${intervalStr}". Use format like "5m", "1h", "30s".`,
534
+ }]);
535
+ return true;
536
+ }
537
+ const task = scheduler.addTask(prompt, intervalMs);
538
+ ctx.setMessages(prev => [...prev, {
539
+ role: 'assistant',
540
+ content: `✓ Task scheduled: "${prompt}" every ${intervalStr} (ID: ${task.id})`,
541
+ }]);
542
+ }
543
+ else {
544
+ ctx.setMessages(prev => [...prev, {
545
+ role: 'assistant',
546
+ content: 'Usage:\n /loop <interval> <prompt> — schedule task\n /loop list — list tasks\n /loop clear — clear all tasks',
547
+ }]);
548
+ }
549
+ return true;
550
+ }
551
+ async function cmdStats(ctx) {
552
+ const mcpTools = mcpManager.getAllTools().length;
553
+ const skills = skillsManager.listSkills().length;
554
+ const agents = subAgentManager['agents'].size;
555
+ const tasks = scheduler.count;
556
+ const exts = extensionManager.listExtensions().length;
557
+ const metrics = ctx.getMetrics?.();
558
+ const usage = metrics?.getTokenUsage();
559
+ const cost = metrics ? metrics.estimatedCostUSD(ctx.config.model) : 0;
560
+ let content = `**Session Statistics:**\n- Messages: ${ctx.messages.length}\n- MCP Tools: ${mcpTools}\n- Skills: ${skills}\n- Subagents: ${agents}\n- Scheduled Tasks: ${tasks}\n- Extensions: ${exts}\n- Theme: ${themeManager.theme.name}\n- Language: ${i18n.getLocale()}\n- Approval Mode: ${ctx.approvalMode}`;
561
+ if (usage && usage.total > 0) {
562
+ content += `\n\n**Token Usage:**\n- Input: ${usage.input.toLocaleString()} tokens\n- Output: ${usage.output.toLocaleString()} tokens\n- Total: ${usage.total.toLocaleString()} tokens\n- Est. Cost: $${cost.toFixed(4)}`;
563
+ }
564
+ ctx.setMessages(prev => [...prev, { role: 'assistant', content }]);
565
+ return true;
566
+ }
567
+ async function cmdTheme(ctx, input) {
568
+ const themeName = input.slice('/theme'.length).trim();
569
+ if (!themeName) {
570
+ if (ctx.onThemePicker) {
571
+ ctx.onThemePicker();
572
+ }
573
+ else {
574
+ const themes = themeManager.listThemes();
575
+ const list = themes.map(t => `- **${t.name}**: ${t.description}`).join('\n');
576
+ ctx.setMessages(prev => [...prev, {
577
+ role: 'assistant',
578
+ content: `**Available Themes:**\n${list}\n\nCurrent: **${themeManager.theme.name}**\nUse \`/theme <name>\` to switch.`,
579
+ }]);
580
+ }
581
+ return true;
582
+ }
583
+ const success = themeManager.setTheme(themeName);
584
+ if (success) {
585
+ ctx.addServiceNotice?.(`🎨 Тема изменена: ${themeName}`);
586
+ ctx.setStatusText(`Тема: ${themeName}`);
587
+ }
588
+ else {
589
+ ctx.addServiceNotice?.(`❌ Тема "${themeName}" не найдена`);
590
+ }
591
+ return true;
592
+ }
593
+ async function cmdModel(ctx, input) {
594
+ const modelId = input.slice('/model'.length).trim();
595
+ if (!modelId) {
596
+ if (ctx.onModelPicker) {
597
+ ctx.onModelPicker();
598
+ }
599
+ else {
600
+ const list = DEEPSEEK_MODELS.map(m => `- **${m.label}** (\`${m.id}\`): ${m.description}${m.id === ctx.config.model ? ' ✓ current' : ''}`).join('\n');
601
+ ctx.setMessages(prev => [...prev, {
602
+ role: 'assistant',
603
+ content: `**Available Models:**\n${list}\n\nCurrent: **${ctx.config.model}**\nUse \`/model <id>\` to switch.`,
604
+ }]);
605
+ }
606
+ return true;
607
+ }
608
+ const found = DEEPSEEK_MODELS.find(m => m.id === modelId || m.label.toLowerCase() === modelId.toLowerCase());
609
+ const targetId = found?.id ?? modelId;
610
+ ctx.config.model = targetId;
611
+ const { saveConfig } = await import('../config/loader.js');
612
+ await saveConfig({ ...ctx.config, model: targetId });
613
+ const label = found?.label ?? targetId;
614
+ ctx.addServiceNotice?.(`🤖 Модель: ${label} (${targetId})`);
615
+ return true;
616
+ }
617
+ async function cmdLang(ctx, input) {
618
+ const code = input.split(/\s+/).pop()?.toLowerCase();
619
+ if (!code || !['en', 'ru', 'zh'].includes(code)) {
620
+ ctx.setMessages(prev => [...prev, {
621
+ role: 'assistant',
622
+ content: 'Usage: `/lang en|ru|zh`',
623
+ }]);
624
+ return true;
625
+ }
626
+ i18n.setLocale(code);
627
+ await saveConfig({ ...ctx.config, language: code });
628
+ ctx.addServiceNotice?.(`🌐 Язык изменён: ${code}`);
629
+ return true;
630
+ }
631
+ async function cmdExtensions(ctx) {
632
+ const exts = extensionManager.listExtensions();
633
+ if (exts.length === 0) {
634
+ ctx.setMessages(prev => [...prev, {
635
+ role: 'assistant',
636
+ content: 'No extensions installed. Create them in `.deepseek-code/extensions/<name>/package.json`.',
637
+ }]);
638
+ }
639
+ else {
640
+ const list = exts.map(e => `- **${e.name}** v${e.version}: ${e.description}`).join('\n');
641
+ ctx.setMessages(prev => [...prev, {
642
+ role: 'assistant',
643
+ content: `**Installed Extensions (${exts.length}):**\n${list}`,
644
+ }]);
645
+ }
646
+ return true;
647
+ }
648
+ async function cmdFollowup(ctx) {
649
+ const lastMsg = ctx.messages.filter(m => m.role === 'assistant').pop();
650
+ const lastContent = lastMsg && typeof lastMsg.content === 'string' ? lastMsg.content : '';
651
+ const suggestions = generateFollowups(lastContent);
652
+ ctx.setMessages(prev => [...prev, {
653
+ role: 'assistant',
654
+ content: `💡 **Follow-up suggestions:**\n${suggestions.map((s, i) => `${i + 1}. ${s}`).join('\n')}`,
655
+ }]);
656
+ return true;
657
+ }
658
+ async function cmdLogs(ctx) {
659
+ const { readdirSync, readFileSync } = await import('node:fs');
660
+ const { join } = await import('node:path');
661
+ const { homedir } = await import('node:os');
662
+ const logDir = join(homedir(), '.deepseek-code', 'logs');
663
+ try {
664
+ const files = readdirSync(logDir).filter(f => f.endsWith('.log')).sort().reverse().slice(0, 10);
665
+ if (files.length === 0) {
666
+ ctx.setMessages(prev => [...prev, {
667
+ role: 'assistant',
668
+ content: 'No log files found in `~/.deepseek-code/logs/`.',
669
+ }]);
670
+ return true;
671
+ }
672
+ const latestLog = readFileSync(join(logDir, files[0]), 'utf-8').slice(-3000);
673
+ const fileList = files.map((f, i) => `${i + 1}. ${f}`).join('\n');
674
+ ctx.setMessages(prev => [...prev, {
675
+ role: 'assistant',
676
+ content: `📋 **Recent Logs** (${files.length} files):\n\n${fileList}\n\n**Tail of ${files[0]}:**\n\`\`\`\n${latestLog}\n\`\`\``,
677
+ }]);
678
+ }
679
+ catch {
680
+ ctx.setMessages(prev => [...prev, {
681
+ role: 'assistant',
682
+ content: 'No log files found. Logging directory does not exist.',
683
+ }]);
684
+ }
685
+ return true;
686
+ }
687
+ async function cmdPlan(ctx) {
688
+ ctx.setMessages(prev => [...prev, {
689
+ role: 'assistant',
690
+ content: 'Use **/capabilities** to see all agent capabilities, or **/stats** for session statistics.',
691
+ }]);
692
+ return true;
693
+ }
694
+ async function cmdTools(ctx) {
695
+ const allTools = getDefaultTools();
696
+ const modeTools = getToolsForMode(ctx.approvalMode);
697
+ const modeToolNames = new Set(modeTools.map(t => t.tool.name));
698
+ const toolLines = allTools.map(def => {
699
+ const t = def.tool;
700
+ const inMode = modeToolNames.has(t.name) ? '✅' : '⛔';
701
+ let approvalLabel;
702
+ if (def.approval === 'never') {
703
+ approvalLabel = 'read-only';
704
+ }
705
+ else if (def.approval === 'auto') {
706
+ approvalLabel = 'auto-approve';
707
+ }
708
+ else {
709
+ approvalLabel = 'ask';
710
+ }
711
+ return ` ${inMode} **${t.name}** — ${t.description} (${approvalLabel})`;
712
+ });
713
+ const content = [
714
+ `**Инструменты агента (${allTools.length} всего, ${modeTools.length} в текущем режиме)**\n`,
715
+ ...toolLines,
716
+ '',
717
+ `**Текущий режим:** \`${ctx.approvalMode}\``,
718
+ ...(ctx.approvalMode === 'plan'
719
+ ? ['> ⚠️ В PLAN mode доступны только read-only инструменты. Для записи используйте `/setup` и смените режим на default/auto-edit/turbo.']
720
+ : []),
721
+ ].filter(Boolean).join('\n');
722
+ ctx.setMessages(prev => [...prev, {
723
+ role: 'assistant',
724
+ content,
725
+ }]);
726
+ return true;
727
+ }
728
+ async function cmdBrowserTest(ctx, input) {
729
+ // Parse flags: /browser-test --headed or /browser-test --headless
730
+ const parts = input.trim().split(/\s+/);
731
+ const flag = parts.length > 1 ? parts[1].toLowerCase() : '';
732
+ let headless;
733
+ if (flag === '--headed') {
734
+ headless = false;
735
+ }
736
+ else if (flag === '--headless') {
737
+ headless = true;
738
+ }
739
+ else {
740
+ headless = false; // default: headed (видимое окно)
741
+ }
742
+ ctx.setStatusText('🧪 Запуск browser test...');
743
+ // Сохраняем текущий режим Chrome, чтобы восстановить после теста
744
+ const prevState = chromeManager.getState();
745
+ const prevHeadless = prevState.headless;
746
+ try {
747
+ const report = await browserTest({ headless });
748
+ ctx.setMessages(prev => [...prev, {
749
+ role: 'assistant',
750
+ content: report,
751
+ }]);
752
+ }
753
+ catch (err) {
754
+ ctx.setMessages(prev => [...prev, {
755
+ role: 'assistant',
756
+ content: `## ❌ Browser Test Error\n\n\`\`\`\n${String(err)}\n\`\`\``,
757
+ }]);
758
+ }
759
+ finally {
760
+ // Восстанавливаем предыдущий режим Chrome (не ломаем агента после теста)
761
+ if (chromeManager.isConnected()) {
762
+ await chromeManager.ensureMode(prevHeadless).catch(() => { });
763
+ }
764
+ }
765
+ ctx.setStatusText('');
766
+ return true;
767
+ }
768
+ async function cmdBrowserRealTest(ctx, input) {
769
+ const parts = input.trim().split(/\s+/).slice(1);
770
+ const saveReport = parts.includes('--save-report');
771
+ const headless = parts.includes('--headless');
772
+ const siteArgs = parts.filter(p => !p.startsWith('--'));
773
+ const sites = siteArgs.length > 0 ? siteArgs : undefined;
774
+ ctx.setStatusText('🌐 Real site smoke-test...');
775
+ const prevState = chromeManager.getState();
776
+ try {
777
+ const report = await browserRealTest({ sites, headless });
778
+ if (saveReport) {
779
+ const { writeFile } = await import('node:fs/promises');
780
+ await writeFile('BROWSER_REAL_TEST_REPORT.md', report, 'utf8');
781
+ ctx.addServiceNotice?.('📄 Report saved: BROWSER_REAL_TEST_REPORT.md');
782
+ }
783
+ ctx.setMessages(prev => [...prev, { role: 'assistant', content: report }]);
784
+ }
785
+ catch (err) {
786
+ ctx.setMessages(prev => [...prev, {
787
+ role: 'assistant',
788
+ content: `## ❌ browser-real-test error\n\`\`\`\n${String(err)}\n\`\`\``,
789
+ }]);
790
+ }
791
+ finally {
792
+ if (chromeManager.isConnected()) {
793
+ await chromeManager.ensureMode(prevState.headless ?? false).catch(() => { });
794
+ }
795
+ ctx.setStatusText('');
796
+ }
797
+ return true;
798
+ }
799
+ async function cmdChrome(ctx, input) {
800
+ const parts = input.trim().split(/\s+/);
801
+ const flag = parts.length > 1 ? parts[1].toLowerCase() : '';
802
+ // No flag — show status
803
+ if (!flag || flag === '--status' || flag === '-s') {
804
+ const state = chromeManager.getState();
805
+ const modeStr = state.connected
806
+ ? (state.headless ? 'headless (фоновый)' : 'headed (видимое окно)')
807
+ : 'не запущен';
808
+ ctx.addServiceNotice?.(`🌐 Chrome: ${modeStr}${state.connected ? ` | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}` : ''}`);
809
+ return true;
810
+ }
811
+ // Determine desired mode
812
+ let desiredHeadless;
813
+ if (flag === '--headless' || flag === '-h') {
814
+ desiredHeadless = true;
815
+ }
816
+ else if (flag === '--headed' || flag === '-v') {
817
+ desiredHeadless = false;
818
+ }
819
+ else {
820
+ ctx.addServiceNotice?.('❌ /chrome: используйте --headed, --headless или без флага для статуса');
821
+ return true;
822
+ }
823
+ try {
824
+ await chromeManager.ensureMode(desiredHeadless);
825
+ const state = chromeManager.getState();
826
+ const modeStr = state.headless ? 'headless (фоновый)' : 'headed (видимое окно)';
827
+ // Save to config
828
+ ctx.config.chromeHeadless = state.headless;
829
+ saveConfig({ chromeHeadless: state.headless }).catch(() => { });
830
+ ctx.addServiceNotice?.(`🌐 Chrome: ${modeStr} | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}`);
831
+ }
832
+ catch (err) {
833
+ ctx.addServiceNotice?.(`❌ Chrome: ${String(err)}`);
834
+ }
835
+ return true;
836
+ }
837
+ async function cmdLastBrowserTest(ctx) {
838
+ const result = getLastBrowserTestResult();
839
+ if (!result) {
840
+ ctx.setMessages(prev => [...prev, {
841
+ role: 'assistant',
842
+ content: '## 📋 Последний browser test\n\nНет сохранённого отчёта последнего теста. Запустите `/browser-test` сначала.',
843
+ }]);
844
+ return true;
845
+ }
846
+ const lines = [
847
+ '## 📋 Последний browser test',
848
+ '',
849
+ `> **Timestamp:** ${result.timestamp}`,
850
+ '> **Источник:** сохранённый structured result (не LLM-реконструкция)',
851
+ '',
852
+ '| Шаг | Статус | Длительность |',
853
+ '|-----|--------|-------------|',
854
+ ];
855
+ for (const step of result.steps) {
856
+ const icon = step.status === 'passed' ? '✅' : step.status === 'failed' ? '❌' : '⏭️';
857
+ const dur = step.durationMs > 0 ? `${step.durationMs}ms` : '—';
858
+ lines.push(`| ${icon} ${step.name} | ${step.status} | ${dur} |`);
859
+ }
860
+ lines.push('');
861
+ lines.push(`**Итого:** ${result.summary.passed} passed, ${result.summary.failed} failed, ${result.summary.skipped} skipped`);
862
+ ctx.setMessages(prev => [...prev, {
863
+ role: 'assistant',
864
+ content: lines.join('\n'),
865
+ }]);
866
+ return true;
867
+ }
868
+ async function cmdCapabilities(ctx) {
869
+ const allTools = getDefaultTools();
870
+ const modeTools = getToolsForMode(ctx.approvalMode);
871
+ const readTools = allTools.filter(t => t.approval === 'never');
872
+ const writeTools = allTools.filter(t => t.approval !== 'never');
873
+ const modeReadTools = modeTools.filter(t => t.approval === 'never');
874
+ const modeWriteTools = modeTools.filter(t => t.approval !== 'never');
875
+ const lines = [
876
+ '## Возможности агента',
877
+ '',
878
+ `**Режим:** \`${ctx.approvalMode}\``,
879
+ '',
880
+ '### Чтение и поиск',
881
+ ...modeReadTools.map(t => ` - ✅ \`${t.tool.name}\` — ${t.tool.description}`),
882
+ ...readTools
883
+ .filter(t => !modeReadTools.some(mt => mt.tool.name === t.tool.name))
884
+ .map(t => ` - ⛔ \`${t.tool.name}\` — заблокирован в PLAN mode`),
885
+ '',
886
+ '### Запись и исполнение',
887
+ ...modeWriteTools.map(t => ` - ✅ \`${t.tool.name}\` — ${t.tool.description} (${t.approval === 'auto' ? 'авто-подтверждение' : 'требует подтверждения'})`),
888
+ ...writeTools
889
+ .filter(t => !modeWriteTools.some(mt => mt.tool.name === t.tool.name))
890
+ .map(t => ` - ⛔ \`${t.tool.name}\` — заблокирован в PLAN mode`),
891
+ '',
892
+ ];
893
+ if (ctx.approvalMode === 'plan') {
894
+ lines.push('> ⚠️ **Вы в PLAN mode.**', '> У меня есть инструменты write_file и edit, но в этом режиме они отключены.', '> Я могу предложить изменения, но не могу применить их напрямую.', '> Используйте `/setup` и выберите другой режим (default, auto-edit, turbo) для включения записи.', '');
895
+ }
896
+ lines.push('### Дополнительно');
897
+ lines.push(' - 🌐 **MCP серверы** — подключаемые внешние инструменты');
898
+ lines.push(' - 🧩 **Расширения** — плагины, добавляющие функциональность');
899
+ lines.push(' - 🧠 **Навыки (Skills)** — предустановленные сценарии работы');
900
+ lines.push(' - 🤖 **Под-агенты** — дочерние агенты для параллельных задач');
901
+ ctx.setMessages(prev => [...prev, {
902
+ role: 'assistant',
903
+ content: lines.join('\n'),
904
+ }]);
905
+ return true;
906
+ }
907
+ export const COMMANDS = [
908
+ { name: '/help', description: 'Show this help', handler: cmdHelp },
909
+ { name: '/setup', description: 'Settings: language, API key, theme, mode', handler: cmdSetup },
910
+ { name: '/remember', description: 'Save to memory: /remember <text>', handler: cmdRemember },
911
+ { name: '/forget', description: 'Delete from memory by search', handler: cmdForget },
912
+ { name: '/memory', description: 'Show all saved memories', handler: cmdMemory },
913
+ { name: '/compress', description: 'Compress chat history', handler: cmdCompress },
914
+ { name: '/checkpoint', description: 'Create git checkpoint', handler: cmdCheckpoint },
915
+ { name: '/restore', description: 'List or restore checkpoint: /restore [id]', handler: cmdRestore },
916
+ { name: '/mcp', description: 'MCP servers: /mcp list | connect', handler: cmdMcp },
917
+ { name: '/skills', description: 'List or describe an agent skill', handler: cmdSkills },
918
+ { name: '/agents', description: 'List active subagents', handler: cmdAgents },
919
+ { name: '/review', description: 'Code review: /review all|diff|auto', handler: cmdReview },
920
+ { name: '/sandbox', description: 'Run command in sandbox', handler: cmdSandbox },
921
+ { name: '/git', description: 'Git: /git commit|branch|diff|status', handler: cmdGit },
922
+ { name: '/loop', description: 'Schedule recurring task: /loop <interval> <prompt>', handler: cmdLoop },
923
+ { name: '/stats', description: 'Session statistics with token usage', handler: cmdStats },
924
+ { name: '/theme', description: 'Switch theme or open picker', handler: cmdTheme },
925
+ { name: '/model', description: 'Switch model or open picker: /model [id]', handler: cmdModel },
926
+ { name: '/lang', description: 'Change language: /lang en|ru|zh', handler: cmdLang },
927
+ { name: '/language', description: 'Alias for /lang', handler: cmdLang },
928
+ { name: '/extensions', description: 'List installed extensions', handler: cmdExtensions },
929
+ { name: '/followup', description: 'Generate follow-up suggestions', handler: cmdFollowup },
930
+ { name: '/logs', description: 'Show recent log files', handler: cmdLogs },
931
+ { name: '/plan', description: 'Show capabilities overview', handler: cmdPlan },
932
+ { name: '/tools', description: 'Show available tools and approval status', handler: cmdTools },
933
+ { name: '/capabilities', description: 'Show full capability matrix', handler: cmdCapabilities },
934
+ { name: '/browser-test', description: 'Run Chrome browser test suite', handler: cmdBrowserTest },
935
+ { name: '/browser-real-test', description: 'Smoke test on real websites', handler: cmdBrowserRealTest },
936
+ { name: '/last-browser-test', description: 'Show last browser test report', handler: cmdLastBrowserTest },
937
+ { name: '/chrome', description: 'Chrome mode: --headed|--headless|-s', handler: cmdChrome },
938
+ ];
939
+ export const COMMAND_NAMES = COMMANDS.map(c => c.name);
940
+ export const COMMAND_MAP = new Map(COMMANDS.map(c => [c.name, c.description]));
941
+ const commandMap = new Map();
942
+ for (const cmd of COMMANDS) {
943
+ commandMap.set(cmd.name, cmd);
944
+ }
945
+ // ─── Aliases ─────────────────────────────────────────────────────────────────
946
+ const commandAliases = new Map([
947
+ ['/h', '/help'],
948
+ ['/?', '/help'],
949
+ ]);
950
+ // ─── Public API ──────────────────────────────────────────────────────────────
951
+ export async function executeSlashCommand(input, ctx) {
952
+ const parts = input.trim().split(/\s+/);
953
+ let cmdName = parts[0]?.toLowerCase();
954
+ // Resolve alias
955
+ const resolved = commandAliases.get(cmdName);
956
+ if (resolved) {
957
+ cmdName = resolved;
958
+ }
959
+ const entry = commandMap.get(cmdName);
960
+ if (!entry)
961
+ return false;
962
+ return entry.handler(ctx, input);
963
+ }
964
+ //# sourceMappingURL=index.js.map