xibecode 0.0.4 → 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 (54) hide show
  1. package/README.md +44 -31
  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 +1 -10
  10. package/dist/commands/mcp.d.ts.map +1 -1
  11. package/dist/commands/mcp.js +76 -102
  12. package/dist/commands/mcp.js.map +1 -1
  13. package/dist/commands/run.d.ts.map +1 -1
  14. package/dist/commands/run.js +10 -8
  15. package/dist/commands/run.js.map +1 -1
  16. package/dist/core/agent.d.ts +5 -0
  17. package/dist/core/agent.d.ts.map +1 -1
  18. package/dist/core/agent.js +16 -4
  19. package/dist/core/agent.js.map +1 -1
  20. package/dist/core/export.d.ts +11 -0
  21. package/dist/core/export.d.ts.map +1 -0
  22. package/dist/core/export.js +54 -0
  23. package/dist/core/export.js.map +1 -0
  24. package/dist/core/mcp-client.d.ts +2 -8
  25. package/dist/core/mcp-client.d.ts.map +1 -1
  26. package/dist/core/mcp-client.js +8 -11
  27. package/dist/core/mcp-client.js.map +1 -1
  28. package/dist/core/session-manager.d.ts +78 -0
  29. package/dist/core/session-manager.d.ts.map +1 -0
  30. package/dist/core/session-manager.js +161 -0
  31. package/dist/core/session-manager.js.map +1 -0
  32. package/dist/core/tools.d.ts +1 -0
  33. package/dist/core/tools.d.ts.map +1 -1
  34. package/dist/core/tools.js +66 -1
  35. package/dist/core/tools.js.map +1 -1
  36. package/dist/index.js +6 -8
  37. package/dist/index.js.map +1 -1
  38. package/dist/ui/enhanced-tui.d.ts +22 -1
  39. package/dist/ui/enhanced-tui.d.ts.map +1 -1
  40. package/dist/ui/enhanced-tui.js +157 -128
  41. package/dist/ui/enhanced-tui.js.map +1 -1
  42. package/dist/ui/themes.d.ts +25 -0
  43. package/dist/ui/themes.d.ts.map +1 -0
  44. package/dist/ui/themes.js +176 -0
  45. package/dist/ui/themes.js.map +1 -0
  46. package/dist/utils/config.d.ts +37 -5
  47. package/dist/utils/config.d.ts.map +1 -1
  48. package/dist/utils/config.js +58 -15
  49. package/dist/utils/config.js.map +1 -1
  50. package/dist/utils/mcp-servers-file.d.ts +11 -4
  51. package/dist/utils/mcp-servers-file.d.ts.map +1 -1
  52. package/dist/utils/mcp-servers-file.js +101 -49
  53. package/dist/utils/mcp-servers-file.js.map +1 -1
  54. 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
@@ -721,22 +719,25 @@ xibecode mcp reload
721
719
  xibecode mcp file
722
720
  ```
723
721
 
724
- #### CLI Method
725
-
726
- Add servers via command line:
722
+ #### Commands
727
723
 
728
724
  ```bash
729
- # Add a server (direct, no prompts)
730
- xibecode mcp add filesystem --command "mcp-server-filesystem" --args "--root /path/to/files"
731
-
732
- # Add with environment variables
733
- xibecode mcp add postgres --command "mcp-server-postgres" --args "--host localhost" --env "PGPASSWORD=secret,PGUSER=admin"
725
+ # Open file to add/edit servers
726
+ xibecode mcp add
727
+ # or
728
+ xibecode mcp edit
734
729
 
735
730
  # List all configured servers
736
731
  xibecode mcp list
737
732
 
738
- # Remove a server
733
+ # Remove a server (or edit file manually)
739
734
  xibecode mcp remove filesystem
735
+
736
+ # Show file path
737
+ xibecode mcp file
738
+
739
+ # Reload after editing
740
+ xibecode mcp reload
740
741
  ```
741
742
 
742
743
  #### Alternative Methods
@@ -761,16 +762,20 @@ MCP servers support two transport types:
761
762
 
762
763
  For local MCP servers that run as a subprocess. Currently only stdio transport is supported:
763
764
 
765
+ **Note:** The MCP configuration now uses a simpler object-based format. Instead of command-line flags, edit the configuration file directly:
766
+
764
767
  ```bash
765
- # Add via command line
766
- xibecode mcp add filesystem --command "mcp-server-filesystem" --args "--root /path/to/files"
768
+ # Open the config file
769
+ xibecode mcp edit
767
770
 
768
- # Configuration format (stored automatically)
771
+ # Add your server to the file using the new format:
769
772
  {
770
- "name": "filesystem",
771
- "transport": "stdio",
772
- "command": "mcp-server-filesystem",
773
- "args": ["--root", "/path/to/files"]
773
+ "mcpServers": {
774
+ "filesystem": {
775
+ "command": "mcp-server-filesystem",
776
+ "args": ["--root", "/path/to/files"]
777
+ }
778
+ }
774
779
  }
775
780
  ```
776
781
 
@@ -800,12 +805,20 @@ MCP tools are prefixed with the server name (e.g., `filesystem::read_file`, `rem
800
805
  # Install the GitHub MCP server
801
806
  npm install -g @modelcontextprotocol/server-github
802
807
 
803
- # Add to XibeCode (easy method - recommended)
804
- xibecode mcp add github --command "mcp-server-github" --args "--token YOUR_GITHUB_TOKEN"
805
-
806
- # Or use interactive method
807
- xibecode config --add-mcp-server github
808
- # Follow prompts: select "stdio", enter command and args
808
+ # Open the config file to add the server
809
+ xibecode mcp edit
810
+ # Edit the file and add:
811
+ # {
812
+ # "mcpServers": {
813
+ # "github": {
814
+ # "command": "mcp-server-github",
815
+ # "args": ["--token", "YOUR_GITHUB_TOKEN"]
816
+ # }
817
+ # }
818
+ # }
819
+
820
+ # Reload servers
821
+ xibecode mcp reload
809
822
 
810
823
  # Now XibeCode can use GitHub tools
811
824
  xibecode chat
@@ -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
  }