xibecode 0.0.5 → 0.0.7

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 (52) hide show
  1. package/README.md +22 -22
  2. package/dist/commands/chat.d.ts +2 -0
  3. package/dist/commands/chat.d.ts.map +1 -1
  4. package/dist/commands/chat.js +362 -80
  5. package/dist/commands/chat.js.map +1 -1
  6. package/dist/commands/config.d.ts.map +1 -1
  7. package/dist/commands/config.js +17 -18
  8. package/dist/commands/config.js.map +1 -1
  9. package/dist/commands/mcp.d.ts.map +1 -1
  10. package/dist/commands/mcp.js +12 -11
  11. package/dist/commands/mcp.js.map +1 -1
  12. package/dist/commands/run.d.ts.map +1 -1
  13. package/dist/commands/run.js +10 -8
  14. package/dist/commands/run.js.map +1 -1
  15. package/dist/core/agent.d.ts +5 -0
  16. package/dist/core/agent.d.ts.map +1 -1
  17. package/dist/core/agent.js +7 -0
  18. package/dist/core/agent.js.map +1 -1
  19. package/dist/core/export.d.ts +11 -0
  20. package/dist/core/export.d.ts.map +1 -0
  21. package/dist/core/export.js +54 -0
  22. package/dist/core/export.js.map +1 -0
  23. package/dist/core/mcp-client.d.ts +2 -8
  24. package/dist/core/mcp-client.d.ts.map +1 -1
  25. package/dist/core/mcp-client.js +8 -11
  26. package/dist/core/mcp-client.js.map +1 -1
  27. package/dist/core/session-manager.d.ts +78 -0
  28. package/dist/core/session-manager.d.ts.map +1 -0
  29. package/dist/core/session-manager.js +161 -0
  30. package/dist/core/session-manager.js.map +1 -0
  31. package/dist/core/tools.d.ts.map +1 -1
  32. package/dist/core/tools.js +6 -7
  33. package/dist/core/tools.js.map +1 -1
  34. package/dist/index.js +3 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/ui/enhanced-tui.d.ts +22 -1
  37. package/dist/ui/enhanced-tui.d.ts.map +1 -1
  38. package/dist/ui/enhanced-tui.js +157 -128
  39. package/dist/ui/enhanced-tui.js.map +1 -1
  40. package/dist/ui/themes.d.ts +25 -0
  41. package/dist/ui/themes.d.ts.map +1 -0
  42. package/dist/ui/themes.js +176 -0
  43. package/dist/ui/themes.js.map +1 -0
  44. package/dist/utils/config.d.ts +37 -5
  45. package/dist/utils/config.d.ts.map +1 -1
  46. package/dist/utils/config.js +58 -15
  47. package/dist/utils/config.js.map +1 -1
  48. package/dist/utils/mcp-servers-file.d.ts +11 -4
  49. package/dist/utils/mcp-servers-file.d.ts.map +1 -1
  50. package/dist/utils/mcp-servers-file.js +101 -49
  51. package/dist/utils/mcp-servers-file.js.map +1 -1
  52. package/package.json +1 -1
package/README.md CHANGED
@@ -642,7 +642,7 @@ XibeCode stores config in `~/.xibecode/`
642
642
  "plugins": [], // Array of plugin paths
643
643
 
644
644
  // Latest version
645
- "mcpServers": [] // MCP server configurations
645
+ "mcpServers": {} // MCP server configurations (object-based)
646
646
  }
647
647
  ```
648
648
 
@@ -688,26 +688,24 @@ nano ~/.xibecode/mcp-servers.json
688
688
 
689
689
  ```json
690
690
  {
691
- "servers": [
692
- {
693
- "name": "filesystem",
694
- "transport": "stdio",
691
+ "mcpServers": {
692
+ "filesystem": {
695
693
  "command": "mcp-server-filesystem",
696
694
  "args": ["--root", "/path/to/files"]
697
695
  },
698
- {
699
- "name": "github",
700
- "transport": "stdio",
696
+ "github": {
701
697
  "command": "mcp-server-github",
702
698
  "args": ["--token", "YOUR_TOKEN"],
703
699
  "env": {
704
700
  "GITHUB_TOKEN": "your_token_here"
705
701
  }
706
702
  }
707
- ]
703
+ }
708
704
  }
709
705
  ```
710
706
 
707
+ > **Note:** The configuration format has been updated to use an object-based structure. If you have an existing configuration using the legacy array format with `"servers": [...]`, it will be automatically migrated to the new format when you run any MCP command.
708
+
711
709
  **File Management Commands:**
712
710
 
713
711
  ```bash
@@ -764,16 +762,20 @@ MCP servers support two transport types:
764
762
 
765
763
  For local MCP servers that run as a subprocess. Currently only stdio transport is supported:
766
764
 
765
+ **Note:** The MCP configuration now uses a simpler object-based format. Instead of command-line flags, edit the configuration file directly:
766
+
767
767
  ```bash
768
- # Add via command line
769
- xibecode mcp add filesystem --command "mcp-server-filesystem" --args "--root /path/to/files"
768
+ # Open the config file
769
+ xibecode mcp edit
770
770
 
771
- # Configuration format (stored automatically)
771
+ # Add your server to the file using the new format:
772
772
  {
773
- "name": "filesystem",
774
- "transport": "stdio",
775
- "command": "mcp-server-filesystem",
776
- "args": ["--root", "/path/to/files"]
773
+ "mcpServers": {
774
+ "filesystem": {
775
+ "command": "mcp-server-filesystem",
776
+ "args": ["--root", "/path/to/files"]
777
+ }
778
+ }
777
779
  }
778
780
  ```
779
781
 
@@ -804,17 +806,15 @@ MCP tools are prefixed with the server name (e.g., `filesystem::read_file`, `rem
804
806
  npm install -g @modelcontextprotocol/server-github
805
807
 
806
808
  # Open the config file to add the server
807
- xibecode mcp add
809
+ xibecode mcp edit
808
810
  # Edit the file and add:
809
811
  # {
810
- # "servers": [
811
- # {
812
- # "name": "github",
813
- # "transport": "stdio",
812
+ # "mcpServers": {
813
+ # "github": {
814
814
  # "command": "mcp-server-github",
815
815
  # "args": ["--token", "YOUR_GITHUB_TOKEN"]
816
816
  # }
817
- # ]
817
+ # }
818
818
  # }
819
819
 
820
820
  # Reload servers
@@ -2,6 +2,8 @@ interface ChatOptions {
2
2
  model?: string;
3
3
  baseUrl?: string;
4
4
  apiKey?: string;
5
+ theme?: string;
6
+ session?: string;
5
7
  }
6
8
  export declare function chatCommand(options: ChatOptions): Promise<void>;
7
9
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAUA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,iBAsVrD"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAcA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,iBAgoBrD"}
@@ -4,14 +4,26 @@ import { CodingToolExecutor } from '../core/tools.js';
4
4
  import { MCPClientManager } from '../core/mcp-client.js';
5
5
  import { EnhancedUI } from '../ui/enhanced-tui.js';
6
6
  import { ConfigManager } from '../utils/config.js';
7
+ import { SessionManager } from '../core/session-manager.js';
8
+ import { exportSessionToMarkdown } from '../core/export.js';
9
+ import { ContextManager } from '../core/context.js';
10
+ import { isThemeName, THEME_NAMES } from '../ui/themes.js';
7
11
  import chalk from 'chalk';
8
12
  import * as fs from 'fs/promises';
9
13
  import * as path from 'path';
10
14
  export async function chatCommand(options) {
11
- const ui = new EnhancedUI(false);
12
15
  const config = new ConfigManager();
16
+ const preferredTheme = (options.theme || config.getTheme());
17
+ const themeName = isThemeName(preferredTheme) ? preferredTheme : 'default';
18
+ const ui = new EnhancedUI(false, themeName);
19
+ ui.setShowDetails(config.getShowDetails());
20
+ ui.setShowThinking(config.getShowThinking());
21
+ const sessionManager = new SessionManager(config.getSessionDirectory());
22
+ const contextManager = new ContextManager(process.cwd());
13
23
  ui.clear();
14
- ui.header('1.0.0');
24
+ if (!config.isHeaderMinimal()) {
25
+ ui.header('1.0.0');
26
+ }
15
27
  // Get API key
16
28
  const apiKey = options.apiKey || config.getApiKey();
17
29
  if (!apiKey) {
@@ -25,16 +37,18 @@ export async function chatCommand(options) {
25
37
  // Load and connect to MCP servers
26
38
  const mcpClientManager = new MCPClientManager();
27
39
  const mcpServers = await config.getMCPServers();
28
- if (mcpServers.length > 0) {
29
- console.log(chalk.blue(` ℹ Connecting to ${mcpServers.length} MCP server(s)...\n`));
30
- for (const serverConfig of mcpServers) {
40
+ const serverNames = Object.keys(mcpServers);
41
+ if (serverNames.length > 0) {
42
+ console.log(chalk.blue(` ℹ Connecting to ${serverNames.length} MCP server(s)...\n`));
43
+ for (const serverName of serverNames) {
44
+ const serverConfig = mcpServers[serverName];
31
45
  try {
32
- await mcpClientManager.connect(serverConfig);
33
- const tools = mcpClientManager.getAvailableTools().filter(t => t.serverName === serverConfig.name);
34
- console.log(chalk.green(` ✓ Connected to ${serverConfig.name} (${tools.length} tool(s))`));
46
+ await mcpClientManager.connect(serverName, serverConfig);
47
+ const tools = mcpClientManager.getAvailableTools().filter(t => t.serverName === serverName);
48
+ console.log(chalk.green(` ✓ Connected to ${serverName} (${tools.length} tool(s))`));
35
49
  }
36
50
  catch (error) {
37
- console.log(chalk.yellow(` ✗ Failed to connect to ${serverConfig.name}: ${error.message}`));
51
+ console.log(chalk.yellow(` ✗ Failed to connect to ${serverName}: ${error.message}`));
38
52
  }
39
53
  }
40
54
  console.log('');
@@ -46,77 +60,96 @@ export async function chatCommand(options) {
46
60
  // ── Create ONE agent for the entire chat session ──
47
61
  // This keeps conversation history (messages) across all turns,
48
62
  // so the AI remembers everything you talked about.
49
- const agent = new EnhancedAgent({
50
- apiKey,
63
+ let agent = new EnhancedAgent({
64
+ apiKey: apiKey,
51
65
  baseUrl,
52
66
  model,
53
67
  maxIterations: 150,
54
68
  verbose: false,
55
69
  });
56
- let hasResponse = false;
57
- // Set up event handlers ONCE (agent is reused across turns)
58
- agent.on('event', (event) => {
59
- switch (event.type) {
60
- case 'thinking':
61
- if (!hasResponse) {
62
- ui.thinking(event.data.message || 'Analyzing your request...');
63
- }
64
- break;
65
- // ── Streaming ──
66
- case 'stream_start':
67
- ui.startAssistantResponse();
68
- hasResponse = true;
69
- break;
70
- case 'stream_text':
71
- ui.streamText(event.data.text);
72
- break;
73
- case 'stream_end':
74
- ui.endAssistantResponse();
75
- break;
76
- // ── Non-streaming fallback ──
77
- case 'response':
78
- if (!hasResponse) {
79
- ui.response(event.data.text);
70
+ function setupAgentHandlers() {
71
+ agent.removeAllListeners('event');
72
+ agent.on('event', (event) => {
73
+ switch (event.type) {
74
+ case 'thinking':
75
+ if (!hasResponse) {
76
+ ui.thinking(event.data.message || 'Analyzing your request...');
77
+ }
78
+ break;
79
+ // ── Streaming ──
80
+ case 'stream_start':
81
+ ui.startAssistantResponse();
80
82
  hasResponse = true;
81
- }
82
- break;
83
- // ── Tools ──
84
- case 'tool_call':
85
- if (enableTools) {
86
- ui.toolCall(event.data.name, event.data.input);
87
- }
88
- break;
89
- case 'tool_result':
90
- if (enableTools) {
91
- ui.toolResult(event.data.name, event.data.result, event.data.success);
92
- const r = event.data.result;
93
- if (r?.success && event.data.name === 'write_file') {
94
- ui.fileChanged('created', r.path, r.lines ? `${r.lines} lines` : undefined);
83
+ break;
84
+ case 'stream_text':
85
+ ui.streamText(event.data.text);
86
+ break;
87
+ case 'stream_end':
88
+ ui.endAssistantResponse();
89
+ break;
90
+ // ── Non-streaming fallback ──
91
+ case 'response':
92
+ if (!hasResponse) {
93
+ ui.response(event.data.text);
94
+ hasResponse = true;
95
95
  }
96
- else if (r?.success && event.data.name === 'edit_file') {
97
- ui.fileChanged('modified', r.path || '', r.linesChanged ? `${r.linesChanged} lines` : undefined);
96
+ break;
97
+ // ── Tools ──
98
+ case 'tool_call':
99
+ if (enableTools) {
100
+ ui.toolCall(event.data.name, event.data.input);
98
101
  }
99
- else if (r?.success && event.data.name === 'edit_lines') {
100
- ui.fileChanged('modified', r.path || '', r.linesChanged ? `${r.linesChanged} lines` : undefined);
102
+ break;
103
+ case 'tool_result':
104
+ if (enableTools) {
105
+ ui.toolResult(event.data.name, event.data.result, event.data.success);
106
+ const r = event.data.result;
107
+ if (r?.success && event.data.name === 'write_file') {
108
+ ui.fileChanged('created', r.path, r.lines ? `${r.lines} lines` : undefined);
109
+ }
110
+ else if (r?.success && event.data.name === 'edit_file') {
111
+ ui.fileChanged('modified', r.path || '', r.linesChanged ? `${r.linesChanged} lines` : undefined);
112
+ }
113
+ else if (r?.success && event.data.name === 'edit_lines') {
114
+ ui.fileChanged('modified', r.path || '', r.linesChanged ? `${r.linesChanged} lines` : undefined);
115
+ }
101
116
  }
102
- }
103
- break;
104
- // ── Iteration ──
105
- case 'iteration':
106
- if (!hasResponse && event.data?.current) {
107
- ui.updateThinking(`Thinking... step ${event.data.current}`);
108
- }
109
- hasResponse = false;
110
- break;
111
- // ── Errors / Warnings ──
112
- case 'error':
113
- ui.error(event.data.message || event.data.error);
114
- break;
115
- case 'warning':
116
- ui.warning(event.data.message);
117
- break;
117
+ break;
118
+ // ── Iteration ──
119
+ case 'iteration':
120
+ if (!hasResponse && event.data?.current) {
121
+ ui.updateThinking(`Thinking... step ${event.data.current}`);
122
+ }
123
+ hasResponse = false;
124
+ break;
125
+ // ── Errors / Warnings ──
126
+ case 'error':
127
+ ui.error(event.data.message || event.data.error);
128
+ break;
129
+ case 'warning':
130
+ ui.warning(event.data.message);
131
+ break;
132
+ }
133
+ });
134
+ }
135
+ let hasResponse = false;
136
+ setupAgentHandlers();
137
+ // ── Session bootstrap ──────────────────────────────────
138
+ let currentSession;
139
+ const existingId = options.session;
140
+ if (existingId) {
141
+ const loaded = await sessionManager.loadSession(existingId);
142
+ if (loaded) {
143
+ currentSession = loaded;
144
+ agent.setMessages(loaded.messages || []);
118
145
  }
119
- });
146
+ else {
147
+ currentSession = await sessionManager.createSession({ model, cwd: process.cwd() });
148
+ }
149
+ }
150
+ else {
151
+ currentSession = await sessionManager.createSession({ model, cwd: process.cwd() });
152
+ }
120
153
  async function showPathSuggestions(raw) {
121
154
  const input = raw.trim().slice(1).trim(); // drop leading '@'
122
155
  const target = input ? path.resolve(process.cwd(), input) : process.cwd();
@@ -157,14 +190,198 @@ export async function chatCommand(options) {
157
190
  console.log('');
158
191
  console.log(chalk.bold(' XibeCode chat commands'));
159
192
  console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
160
- console.log(' ' + chalk.hex('#00D4FF')('/help') + chalk.hex('#6B6B7B')(' show this help, not an AI reply'));
161
- console.log(' ' + chalk.hex('#00D4FF')('/mcp') + chalk.hex('#6B6B7B')(' show connected MCP servers and tools'));
162
- console.log(' ' + chalk.hex('#00D4FF')('@path') + chalk.hex('#6B6B7B')(' list files/folders under path (or cwd if just "@")'));
163
- console.log(' ' + chalk.hex('#00D4FF')('clear') + chalk.hex('#6B6B7B')(' clear screen and redraw header'));
164
- console.log(' ' + chalk.hex('#00D4FF')('tools on') + chalk.hex('#6B6B7B')(' enable editor & filesystem tools'));
165
- console.log(' ' + chalk.hex('#00D4FF')('tools off') + chalk.hex('#6B6B7B')(' disable tools (chat only)'));
166
- console.log(' ' + chalk.hex('#00D4FF')('exit / quit') + chalk.hex('#6B6B7B')(' end the chat session'));
193
+ console.log(' ' + chalk.hex('#00D4FF')('/help') + chalk.hex('#6B6B7B')(' show this help, not an AI reply'));
194
+ console.log(' ' + chalk.hex('#00D4FF')('/mcp') + chalk.hex('#6B6B7B')(' show connected MCP servers and tools'));
195
+ console.log(' ' + chalk.hex('#00D4FF')('/new') + chalk.hex('#6B6B7B')(' start a new chat session'));
196
+ console.log(' ' + chalk.hex('#00D4FF')('/sessions') + chalk.hex('#6B6B7B')(' list and switch saved sessions'));
197
+ console.log(' ' + chalk.hex('#00D4FF')('/models') + chalk.hex('#6B6B7B')(' show/switch models'));
198
+ console.log(' ' + chalk.hex('#00D4FF')('/export') + chalk.hex('#6B6B7B')(' export this session to Markdown'));
199
+ console.log(' ' + chalk.hex('#00D4FF')('/compact') + chalk.hex('#6B6B7B')(' compact long conversation history'));
200
+ console.log(' ' + chalk.hex('#00D4FF')('/details') + chalk.hex('#6B6B7B')(' toggle verbose tool details'));
201
+ console.log(' ' + chalk.hex('#00D4FF')('/thinking') + chalk.hex('#6B6B7B')(' toggle thinking spinner'));
202
+ console.log(' ' + chalk.hex('#00D4FF')('/themes') + chalk.hex('#6B6B7B')(' choose a color theme'));
203
+ console.log(' ' + chalk.hex('#00D4FF')('@path') + chalk.hex('#6B6B7B')(' list files/folders under path (or cwd if just "@")'));
204
+ console.log(' ' + chalk.hex('#00D4FF')('clear') + chalk.hex('#6B6B7B')(' clear screen and redraw header'));
205
+ console.log(' ' + chalk.hex('#00D4FF')('tools on/off') + chalk.hex('#6B6B7B')(' toggle tools (editor & filesystem)'));
206
+ console.log(' ' + chalk.hex('#00D4FF')('exit / quit') + chalk.hex('#6B6B7B')(' end the chat session'));
207
+ console.log(' ' + chalk.hex('#00D4FF')('!cmd') + chalk.hex('#6B6B7B')(' run a shell command and feed output to AI'));
208
+ console.log('');
209
+ }
210
+ async function handleShellBang(input) {
211
+ const cmd = input.slice(1).trim();
212
+ if (!cmd) {
213
+ ui.warning('No command provided after "!". Example: !ls -la');
214
+ return;
215
+ }
216
+ ui.info(`Running shell command: ${cmd}`);
217
+ const result = await toolExecutor.execute('run_command', { command: cmd, cwd: process.cwd(), timeout: 300 });
218
+ const stdout = result.stdout || '';
219
+ const stderr = result.stderr || '';
220
+ console.log('');
221
+ console.log(chalk.bold(' Command Output'));
222
+ console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
223
+ if (stdout) {
224
+ console.log(chalk.white(stdout));
225
+ }
226
+ if (stderr) {
227
+ console.log(chalk.red(stderr));
228
+ }
167
229
  console.log('');
230
+ const summary = [
231
+ `Shell command: ${cmd}`,
232
+ '',
233
+ stdout ? stdout : '',
234
+ stderr ? `STDERR:\n${stderr}` : '',
235
+ ].join('\n');
236
+ const tools = enableTools ? toolExecutor.getTools() : [];
237
+ await agent.run(summary, tools, toolExecutor);
238
+ const stats = agent.getStats();
239
+ if (config.isStatusBarEnabled()) {
240
+ ui.renderStatusBar({
241
+ model,
242
+ sessionTitle: currentSession.title,
243
+ cwd: process.cwd(),
244
+ toolsEnabled: enableTools,
245
+ themeName: ui.getThemeName(),
246
+ });
247
+ }
248
+ await sessionManager.saveMessagesAndStats({
249
+ id: currentSession.id,
250
+ messages: agent.getMessages(),
251
+ stats,
252
+ });
253
+ }
254
+ async function handleSessionsCommand() {
255
+ const sessions = await sessionManager.listSessions();
256
+ if (sessions.length === 0) {
257
+ ui.info('No saved sessions yet.');
258
+ return;
259
+ }
260
+ const { picked } = await inquirer.prompt([
261
+ {
262
+ type: 'list',
263
+ name: 'picked',
264
+ message: 'Select session',
265
+ choices: sessions.map(s => ({
266
+ name: `${s.title} · ${s.model} · ${s.updated}`,
267
+ value: s.id,
268
+ })),
269
+ },
270
+ ]);
271
+ const loaded = await sessionManager.loadSession(picked);
272
+ if (!loaded) {
273
+ ui.error('Failed to load selected session');
274
+ return;
275
+ }
276
+ currentSession = loaded;
277
+ agent.setMessages(loaded.messages || []);
278
+ ui.success(`Switched to session ${loaded.title}`);
279
+ }
280
+ async function handleNewSession() {
281
+ currentSession = await sessionManager.createSession({ model, cwd: process.cwd() });
282
+ agent = new EnhancedAgent({
283
+ apiKey: apiKey,
284
+ baseUrl,
285
+ model,
286
+ maxIterations: 150,
287
+ verbose: false,
288
+ });
289
+ setupAgentHandlers();
290
+ ui.success('Started new session');
291
+ }
292
+ async function handleModelsCommand() {
293
+ const current = model;
294
+ const models = [
295
+ current,
296
+ 'claude-sonnet-4-5-20250929',
297
+ 'claude-opus-4-5-20251101',
298
+ 'claude-haiku-4-5-20251015',
299
+ ];
300
+ const unique = Array.from(new Set(models));
301
+ const { picked } = await inquirer.prompt([
302
+ {
303
+ type: 'list',
304
+ name: 'picked',
305
+ message: 'Select model',
306
+ choices: unique.map(m => ({
307
+ name: m === current ? `${m} (current)` : m,
308
+ value: m,
309
+ })),
310
+ },
311
+ ]);
312
+ config.set('model', picked);
313
+ ui.success(`Model set to: ${picked}`);
314
+ }
315
+ async function handleThemesCommand() {
316
+ const current = ui.getThemeName();
317
+ const { picked } = await inquirer.prompt([
318
+ {
319
+ type: 'list',
320
+ name: 'picked',
321
+ message: 'Select theme',
322
+ choices: THEME_NAMES.map(name => ({
323
+ name: name === current ? `${name} (current)` : name,
324
+ value: name,
325
+ })),
326
+ },
327
+ ]);
328
+ ui.setTheme(picked);
329
+ config.set('theme', picked);
330
+ ui.success(`Theme set to: ${picked}`);
331
+ }
332
+ async function handleExportCommand() {
333
+ const session = {
334
+ ...currentSession,
335
+ messages: agent.getMessages(),
336
+ };
337
+ const markdown = exportSessionToMarkdown(session);
338
+ const exportsDir = path.join(config['getConfigPath'], '..', 'sessions');
339
+ const fileName = `${session.id}.md`;
340
+ const fullPath = path.join(exportsDir, fileName);
341
+ await fs.mkdir(exportsDir, { recursive: true });
342
+ await fs.writeFile(fullPath, markdown, 'utf-8');
343
+ ui.success(`Session exported to ${fullPath}`);
344
+ }
345
+ async function handleCompactCommand() {
346
+ const messages = agent.getMessages();
347
+ if (messages.length <= 10) {
348
+ ui.info('Conversation is short; no compaction needed.');
349
+ return;
350
+ }
351
+ const preserved = messages.slice(-6);
352
+ const summaryMessage = {
353
+ role: 'assistant',
354
+ content: 'Earlier conversation has been compacted to save context. Key details from the last messages are preserved.',
355
+ };
356
+ const compacted = [summaryMessage, ...preserved];
357
+ agent.setMessages(compacted);
358
+ await sessionManager.saveMessagesAndStats({
359
+ id: currentSession.id,
360
+ messages: compacted,
361
+ stats: agent.getStats(),
362
+ });
363
+ ui.success('Conversation compacted.');
364
+ }
365
+ async function handleAtPathFuzzy(raw) {
366
+ const input = raw.trim().slice(1).trim();
367
+ const pattern = input ? `**/*${input}*` : '**/*';
368
+ try {
369
+ const files = await contextManager.searchFiles(pattern, { maxResults: 100 });
370
+ if (!files.length) {
371
+ ui.info(`No matches for pattern ${pattern}`);
372
+ return;
373
+ }
374
+ console.log('');
375
+ console.log(' ' + chalk.bold('Files'));
376
+ console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
377
+ files.forEach(f => {
378
+ console.log(' ' + chalk.hex('#CE93D8')('📄') + ' ' + chalk.white(f));
379
+ });
380
+ console.log('');
381
+ }
382
+ catch (error) {
383
+ ui.error('Failed to search files for @ path', error);
384
+ }
168
385
  }
169
386
  // ── Chat loop ──
170
387
  while (true) {
@@ -227,6 +444,44 @@ export async function chatCommand(options) {
227
444
  showSlashHelp();
228
445
  continue;
229
446
  }
447
+ if (lowerMessage === '/new') {
448
+ await handleNewSession();
449
+ continue;
450
+ }
451
+ if (lowerMessage === '/sessions') {
452
+ await handleSessionsCommand();
453
+ continue;
454
+ }
455
+ if (lowerMessage === '/models') {
456
+ await handleModelsCommand();
457
+ continue;
458
+ }
459
+ if (lowerMessage === '/themes') {
460
+ await handleThemesCommand();
461
+ continue;
462
+ }
463
+ if (lowerMessage === '/export') {
464
+ await handleExportCommand();
465
+ continue;
466
+ }
467
+ if (lowerMessage === '/compact') {
468
+ await handleCompactCommand();
469
+ continue;
470
+ }
471
+ if (lowerMessage === '/details') {
472
+ const next = !ui.getShowDetails();
473
+ ui.setShowDetails(next);
474
+ config.set('showDetails', next);
475
+ ui.success(`Details ${next ? 'enabled' : 'disabled'}`);
476
+ continue;
477
+ }
478
+ if (lowerMessage === '/thinking') {
479
+ const next = !ui.getShowThinking();
480
+ ui.setShowThinking(next);
481
+ config.set('showThinking', next);
482
+ ui.success(`Thinking display ${next ? 'enabled' : 'disabled'}`);
483
+ continue;
484
+ }
230
485
  if (lowerMessage === '/mcp') {
231
486
  console.log('');
232
487
  console.log(chalk.bold(' MCP Servers'));
@@ -256,7 +511,12 @@ export async function chatCommand(options) {
256
511
  continue;
257
512
  }
258
513
  if (trimmed.startsWith('@')) {
259
- await showPathSuggestions(trimmed);
514
+ await handleAtPathFuzzy(trimmed);
515
+ continue;
516
+ }
517
+ if (trimmed.startsWith('!')) {
518
+ await handleShellBang(trimmed);
519
+ console.log('');
260
520
  continue;
261
521
  }
262
522
  if (lowerMessage === 'exit' || lowerMessage === 'quit') {
@@ -270,11 +530,18 @@ export async function chatCommand(options) {
270
530
  console.log(chalk.hex('#3A3A4A')(' │') + chalk.hex('#00D4FF')(' 👋 See you next time! ') + chalk.hex('#3A3A4A')('│'));
271
531
  console.log(chalk.hex('#3A3A4A')(' ╰──────────────────────────────────╯'));
272
532
  console.log('');
533
+ await sessionManager.saveMessagesAndStats({
534
+ id: currentSession.id,
535
+ messages: agent.getMessages(),
536
+ stats,
537
+ });
273
538
  break;
274
539
  }
275
540
  if (lowerMessage === 'clear') {
276
541
  ui.clear();
277
- ui.header('1.0.0');
542
+ if (!config.isHeaderMinimal()) {
543
+ ui.header('1.0.0');
544
+ }
278
545
  ui.chatBanner(process.cwd(), model, baseUrl);
279
546
  continue;
280
547
  }
@@ -296,6 +563,21 @@ export async function chatCommand(options) {
296
563
  // the conversation history (this.messages), so the AI has
297
564
  // full context of everything discussed in this session.
298
565
  await agent.run(message, tools, toolExecutor);
566
+ const stats = agent.getStats();
567
+ if (config.isStatusBarEnabled()) {
568
+ ui.renderStatusBar({
569
+ model,
570
+ sessionTitle: currentSession.title,
571
+ cwd: process.cwd(),
572
+ toolsEnabled: enableTools,
573
+ themeName: ui.getThemeName(),
574
+ });
575
+ }
576
+ await sessionManager.saveMessagesAndStats({
577
+ id: currentSession.id,
578
+ messages: agent.getMessages(),
579
+ stats,
580
+ });
299
581
  }
300
582
  catch (error) {
301
583
  ui.error('Failed to process message', error);
@@ -303,7 +585,7 @@ export async function chatCommand(options) {
303
585
  console.log('');
304
586
  }
305
587
  // Cleanup: disconnect from all MCP servers
306
- if (mcpServers.length > 0) {
588
+ if (serverNames.length > 0) {
307
589
  await mcpClientManager.disconnectAll();
308
590
  }
309
591
  }