xibecode 0.7.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +20 -44
  2. package/dist/commands/chat.d.ts.map +1 -1
  3. package/dist/commands/chat.js +9 -1462
  4. package/dist/commands/chat.js.map +1 -1
  5. package/dist/commands/run-pr.d.ts.map +1 -1
  6. package/dist/commands/run-pr.js +9 -1
  7. package/dist/commands/run-pr.js.map +1 -1
  8. package/dist/commands/run.d.ts.map +1 -1
  9. package/dist/commands/run.js +46 -1
  10. package/dist/commands/run.js.map +1 -1
  11. package/dist/components/AssistantMarkdown.d.ts +10 -0
  12. package/dist/components/AssistantMarkdown.d.ts.map +1 -0
  13. package/dist/components/AssistantMarkdown.js +25 -0
  14. package/dist/components/AssistantMarkdown.js.map +1 -0
  15. package/dist/components/design-system/ThemeProvider.d.ts +13 -0
  16. package/dist/components/design-system/ThemeProvider.d.ts.map +1 -0
  17. package/dist/components/design-system/ThemeProvider.js +21 -0
  18. package/dist/components/design-system/ThemeProvider.js.map +1 -0
  19. package/dist/components/design-system/ThemedBox.d.ts +18 -0
  20. package/dist/components/design-system/ThemedBox.d.ts.map +1 -0
  21. package/dist/components/design-system/ThemedBox.js +27 -0
  22. package/dist/components/design-system/ThemedBox.js.map +1 -0
  23. package/dist/components/design-system/ThemedText.d.ts +11 -0
  24. package/dist/components/design-system/ThemedText.d.ts.map +1 -0
  25. package/dist/components/design-system/ThemedText.js +23 -0
  26. package/dist/components/design-system/ThemedText.js.map +1 -0
  27. package/dist/core/agent-tool-policies.d.ts +5 -0
  28. package/dist/core/agent-tool-policies.d.ts.map +1 -0
  29. package/dist/core/agent-tool-policies.js +18 -0
  30. package/dist/core/agent-tool-policies.js.map +1 -0
  31. package/dist/core/agent.d.ts +30 -0
  32. package/dist/core/agent.d.ts.map +1 -1
  33. package/dist/core/agent.js +320 -100
  34. package/dist/core/agent.js.map +1 -1
  35. package/dist/core/background-agent.d.ts.map +1 -1
  36. package/dist/core/background-agent.js +4 -0
  37. package/dist/core/background-agent.js.map +1 -1
  38. package/dist/core/context-compactor.d.ts +10 -0
  39. package/dist/core/context-compactor.d.ts.map +1 -0
  40. package/dist/core/context-compactor.js +158 -0
  41. package/dist/core/context-compactor.js.map +1 -0
  42. package/dist/core/conversation-recovery.d.ts +9 -0
  43. package/dist/core/conversation-recovery.d.ts.map +1 -0
  44. package/dist/core/conversation-recovery.js +15 -0
  45. package/dist/core/conversation-recovery.js.map +1 -0
  46. package/dist/core/debug-workflow.d.ts +9 -0
  47. package/dist/core/debug-workflow.d.ts.map +1 -0
  48. package/dist/core/debug-workflow.js +19 -0
  49. package/dist/core/debug-workflow.js.map +1 -0
  50. package/dist/core/memory-promotions.d.ts +15 -0
  51. package/dist/core/memory-promotions.d.ts.map +1 -0
  52. package/dist/core/memory-promotions.js +38 -0
  53. package/dist/core/memory-promotions.js.map +1 -0
  54. package/dist/core/memory.d.ts +3 -1
  55. package/dist/core/memory.d.ts.map +1 -1
  56. package/dist/core/memory.js +27 -3
  57. package/dist/core/memory.js.map +1 -1
  58. package/dist/core/modes.d.ts +1 -0
  59. package/dist/core/modes.d.ts.map +1 -1
  60. package/dist/core/modes.js +94 -5
  61. package/dist/core/modes.js.map +1 -1
  62. package/dist/core/permission-store.d.ts +15 -0
  63. package/dist/core/permission-store.d.ts.map +1 -0
  64. package/dist/core/permission-store.js +30 -0
  65. package/dist/core/permission-store.js.map +1 -0
  66. package/dist/core/permissions.d.ts +33 -0
  67. package/dist/core/permissions.d.ts.map +1 -0
  68. package/dist/core/permissions.js +139 -0
  69. package/dist/core/permissions.js.map +1 -0
  70. package/dist/core/plan-artifacts.d.ts +10 -0
  71. package/dist/core/plan-artifacts.d.ts.map +1 -0
  72. package/dist/core/plan-artifacts.js +53 -0
  73. package/dist/core/plan-artifacts.js.map +1 -0
  74. package/dist/core/plan-session.d.ts +25 -0
  75. package/dist/core/plan-session.d.ts.map +1 -0
  76. package/dist/core/plan-session.js +95 -0
  77. package/dist/core/plan-session.js.map +1 -0
  78. package/dist/core/planMode.d.ts +5 -0
  79. package/dist/core/planMode.d.ts.map +1 -1
  80. package/dist/core/planMode.js +52 -1
  81. package/dist/core/planMode.js.map +1 -1
  82. package/dist/core/session-bridge.d.ts +12 -1
  83. package/dist/core/session-bridge.d.ts.map +1 -1
  84. package/dist/core/session-bridge.js +46 -0
  85. package/dist/core/session-bridge.js.map +1 -1
  86. package/dist/core/session-manager.d.ts +2 -0
  87. package/dist/core/session-manager.d.ts.map +1 -1
  88. package/dist/core/session-manager.js +3 -1
  89. package/dist/core/session-manager.js.map +1 -1
  90. package/dist/core/swarm.d.ts.map +1 -1
  91. package/dist/core/swarm.js +2 -0
  92. package/dist/core/swarm.js.map +1 -1
  93. package/dist/core/task-status.d.ts +13 -0
  94. package/dist/core/task-status.d.ts.map +1 -0
  95. package/dist/core/task-status.js +17 -0
  96. package/dist/core/task-status.js.map +1 -0
  97. package/dist/core/tool-orchestrator.d.ts +22 -0
  98. package/dist/core/tool-orchestrator.d.ts.map +1 -0
  99. package/dist/core/tool-orchestrator.js +56 -0
  100. package/dist/core/tool-orchestrator.js.map +1 -0
  101. package/dist/core/tools.d.ts +6 -0
  102. package/dist/core/tools.d.ts.map +1 -1
  103. package/dist/core/tools.js +30 -1
  104. package/dist/core/tools.js.map +1 -1
  105. package/dist/core/transcript-cleanup.d.ts +8 -0
  106. package/dist/core/transcript-cleanup.d.ts.map +1 -0
  107. package/dist/core/transcript-cleanup.js +52 -0
  108. package/dist/core/transcript-cleanup.js.map +1 -0
  109. package/dist/ink.d.ts +24 -0
  110. package/dist/ink.d.ts.map +1 -0
  111. package/dist/ink.js +56 -0
  112. package/dist/ink.js.map +1 -0
  113. package/dist/interactiveHelpers.d.ts +9 -0
  114. package/dist/interactiveHelpers.d.ts.map +1 -0
  115. package/dist/interactiveHelpers.js +20 -0
  116. package/dist/interactiveHelpers.js.map +1 -0
  117. package/dist/types/command.d.ts +24 -0
  118. package/dist/types/command.d.ts.map +1 -0
  119. package/dist/types/command.js +2 -0
  120. package/dist/types/command.js.map +1 -0
  121. package/dist/ui/claude-style-chat.d.ts +11 -0
  122. package/dist/ui/claude-style-chat.d.ts.map +1 -0
  123. package/dist/ui/claude-style-chat.js +492 -0
  124. package/dist/ui/claude-style-chat.js.map +1 -0
  125. package/dist/utils/config.d.ts +6 -1
  126. package/dist/utils/config.d.ts.map +1 -1
  127. package/dist/utils/config.js +1 -1
  128. package/dist/utils/config.js.map +1 -1
  129. package/dist/utils/renderOptions.d.ts +7 -0
  130. package/dist/utils/renderOptions.d.ts.map +1 -0
  131. package/dist/utils/renderOptions.js +60 -0
  132. package/dist/utils/renderOptions.js.map +1 -0
  133. package/dist/utils/tool-display.d.ts +8 -0
  134. package/dist/utils/tool-display.d.ts.map +1 -0
  135. package/dist/utils/tool-display.js +213 -0
  136. package/dist/utils/tool-display.js.map +1 -0
  137. package/dist/utils/tui-theme.d.ts +78 -0
  138. package/dist/utils/tui-theme.d.ts.map +1 -0
  139. package/dist/utils/tui-theme.js +76 -0
  140. package/dist/utils/tui-theme.js.map +1 -0
  141. package/dist/webui/server.d.ts.map +1 -1
  142. package/dist/webui/server.js +2 -1
  143. package/dist/webui/server.js.map +1 -1
  144. package/package.json +8 -6
@@ -1,1467 +1,14 @@
1
- import inquirer from 'inquirer';
2
- import readline from 'readline';
3
- import { EnhancedAgent } from '../core/agent.js';
4
- import { CodingToolExecutor } from '../core/tools.js';
5
- import { MCPClientManager } from '../core/mcp-client.js';
6
- import { EnhancedUI } from '../ui/enhanced-tui.js';
7
- import { ConfigManager } from '../utils/config.js';
8
- import { SessionManager } from '../core/session-manager.js';
9
- import { exportSessionToMarkdown } from '../core/export.js';
10
- import { ContextManager } from '../core/context.js';
11
- import { PlanMode } from '../core/planMode.js';
12
- import { TodoManager } from '../utils/todoManager.js';
13
- import { getAllModes, MODE_CONFIG } from '../core/modes.js';
14
- import { isThemeName, THEME_NAMES } from '../ui/themes.js';
15
- import { SkillManager } from '../core/skills.js';
16
- import { startWebUI } from '../webui/server.js';
17
- import { SessionBridge } from '../core/session-bridge.js';
18
- import chalk from 'chalk';
19
- import * as fs from 'fs/promises';
20
- import * as path from 'path';
1
+ import { launchClaudeStyleChat } from '../ui/claude-style-chat.js';
2
+ import { createRoot } from '../ink.js';
3
+ import { exitWithMessage } from '../interactiveHelpers.js';
21
4
  export async function chatCommand(options) {
22
- const config = new ConfigManager();
23
- const preferredTheme = (options.theme || config.getTheme());
24
- const themeName = isThemeName(preferredTheme) ? preferredTheme : 'default';
25
- const ui = new EnhancedUI(false, themeName);
26
- ui.setShowDetails(config.getShowDetails());
27
- ui.setShowThinking(config.getShowThinking());
28
- // Condensed mode is a chat UX toggle to drastically reduce tool/diff spam.
29
- // Kept OFF by default to avoid changing existing behavior unexpectedly.
30
- let condensedUI = false;
31
- ui.setCondensedUI(condensedUI);
32
- const sessionManager = new SessionManager(config.getSessionDirectory());
33
- const contextManager = new ContextManager(process.cwd());
34
- let skillManager;
35
- let webUIServer = null;
36
- ui.clear();
37
- if (!config.isHeaderMinimal()) {
38
- ui.header('1.0.0');
5
+ try {
6
+ await launchClaudeStyleChat(options);
39
7
  }
40
- // Start WebUI server in background (unless disabled)
41
- const webUIPort = 3847;
42
- if (!options.noWebui) {
43
- try {
44
- webUIServer = await startWebUI({ port: webUIPort, host: 'localhost', workingDir: process.cwd() });
45
- console.log(chalk.hex('#6B6B7B')(` WebUI: `) + chalk.hex('#00D4FF')(`http://localhost:${webUIPort}`) + chalk.hex('#6B6B7B')(` (open in browser for visual interface)`));
46
- console.log('');
47
- }
48
- catch (error) {
49
- // Silently continue if WebUI fails to start (port might be in use)
50
- if (error.code !== 'EADDRINUSE') {
51
- console.log(chalk.hex('#6B6B7B')(` WebUI: `) + chalk.yellow(`failed to start (${error.message})`));
52
- console.log('');
53
- }
54
- }
55
- }
56
- // Get API key
57
- const apiKey = options.apiKey || config.getApiKey();
58
- if (!apiKey) {
59
- ui.error('No API key found!');
60
- console.log(chalk.white(' Set your API key:\n'));
61
- console.log(chalk.cyan(' xibecode config --set-key YOUR_KEY\n'));
62
- process.exit(1);
63
- }
64
- const useEconomy = (options.costMode || config.getCostMode()) === 'economy';
65
- const model = options.model || config.getModel(useEconomy);
66
- const baseUrl = options.baseUrl || config.getBaseUrl();
67
- let currentProvider = options.provider || config.get('provider');
68
- skillManager = new SkillManager(process.cwd(), apiKey, baseUrl, model, currentProvider);
69
- await skillManager.loadSkills();
70
- const defaultSkillsPrompt = await skillManager.buildDefaultSkillsPromptForTask('', process.cwd());
71
- // Initialize MCP client manager.
72
- // Connections are established on-demand (for example when the user runs /mcp),
73
- // instead of eagerly on startup.
74
- const mcpClientManager = new MCPClientManager();
75
- const planMode = new PlanMode(process.cwd(), {
76
- apiKey: apiKey,
77
- baseUrl,
78
- model,
79
- maxIterations: 10,
80
- verbose: false,
81
- }, currentProvider || 'anthropic');
82
- // Gemini‑style intro screen
83
- ui.chatBanner(process.cwd(), model, baseUrl);
84
- let enableTools = true;
85
- const toolExecutor = new CodingToolExecutor(process.cwd(), { mcpClientManager, skillManager });
86
- // ── Undo/Redo history stack ──
87
- const undoStack = [];
88
- const redoStack = [];
89
- const MAX_UNDO = 20;
90
- // ── Create ONE agent for the entire chat session ──
91
- // This keeps conversation history (messages) across all turns,
92
- // so the AI remembers everything you talked about.
93
- let agent = new EnhancedAgent({
94
- apiKey: apiKey,
95
- baseUrl,
96
- model,
97
- maxIterations: 150,
98
- verbose: false,
99
- provider: currentProvider,
100
- customProviderFormat: config.get('customProviderFormat'),
101
- defaultSkillsPrompt,
102
- }, currentProvider);
103
- const allModes = getAllModes();
104
- let currentMode = agent.getMode();
105
- // Buffer to accumulate streamed text for plan parsing
106
- let streamBuffer = '';
107
- function parsePlannerOutput(text) {
108
- // Check for [[QUESTIONS: {...}]] block
109
- const questionsMatch = text.match(/\[\[QUESTIONS:\s*([\s\S]*?)\]\]/);
110
- if (questionsMatch) {
111
- try {
112
- const parsed = JSON.parse(questionsMatch[1]);
113
- const questions = parsed.questions || [];
114
- if (questions.length > 0) {
115
- SessionBridge.onPlanQuestions(questions);
116
- // In CLI mode, handle questions via inquirer
117
- handlePlannerQuestions(questions).catch(() => { });
118
- }
119
- }
120
- catch (e) {
121
- // JSON parse failed, ignore
122
- }
123
- }
124
- // Check for [[PLAN_READY]] tag
125
- if (text.includes('[[PLAN_READY]]')) {
126
- // Read the implementations.md file content
127
- const fs = require('fs');
128
- const path = require('path');
129
- const planPath = path.join(process.cwd(), 'implementations.md');
130
- try {
131
- const planContent = fs.readFileSync(planPath, 'utf-8');
132
- SessionBridge.onPlanReady(planContent, 'implementations.md');
133
- }
134
- catch {
135
- // File might not exist yet
136
- }
137
- }
138
- // Check for [[PENTEST_READY]] tag
139
- if (text.includes('[[PENTEST_READY]]')) {
140
- const fs = require('fs');
141
- const path = require('path');
142
- const reportPath = path.join(process.cwd(), 'pentest-report.md');
143
- try {
144
- const reportContent = fs.readFileSync(reportPath, 'utf-8');
145
- SessionBridge.onPentestReady(reportContent, 'pentest-report.md');
146
- }
147
- catch {
148
- // File might not exist yet
149
- }
150
- }
151
- }
152
- async function handlePlannerQuestions(questions) {
153
- try {
154
- closeRL();
155
- const inquirer = (await import('inquirer')).default;
156
- const answers = {};
157
- for (const q of questions) {
158
- const choices = q.options.map((o) => ({ name: o.label, value: o.id }));
159
- if (q.hasOther) {
160
- choices.push({ name: 'Other (please type)', value: '__other__' });
161
- }
162
- if (q.allowMultiple) {
163
- const result = await inquirer.prompt([{
164
- type: 'checkbox',
165
- name: 'answer',
166
- message: q.question,
167
- choices,
168
- }]);
169
- const selected = result.answer;
170
- if (selected.includes('__other__')) {
171
- const otherResult = await inquirer.prompt([{
172
- type: 'input',
173
- name: 'other',
174
- message: 'Please specify:',
175
- }]);
176
- answers[q.id] = [...selected.filter((s) => s !== '__other__'), otherResult.other].join(', ');
177
- }
178
- else {
179
- answers[q.id] = selected.join(', ');
180
- }
181
- }
182
- else {
183
- const result = await inquirer.prompt([{
184
- type: 'list',
185
- name: 'answer',
186
- message: q.question,
187
- choices,
188
- }]);
189
- if (result.answer === '__other__') {
190
- const otherResult = await inquirer.prompt([{
191
- type: 'input',
192
- name: 'other',
193
- message: 'Please specify:',
194
- }]);
195
- answers[q.id] = otherResult.other;
196
- }
197
- else {
198
- answers[q.id] = result.answer;
199
- }
200
- }
201
- }
202
- // Inject answers back into the conversation
203
- const answerText = Object.entries(answers)
204
- .map(([id, val]) => `${id}: ${val}`)
205
- .join('\n');
206
- const message = `Here are my answers to your questions:\n${answerText}`;
207
- // Send as a user message
208
- const tools = enableTools ? toolExecutor.getTools() : [];
209
- agent.getMessages().push({ role: 'user', content: message });
210
- await agent.run(message, tools, toolExecutor);
211
- }
212
- catch {
213
- // Inquirer not available or error
214
- }
215
- finally {
216
- createRL();
217
- }
218
- }
219
- function setupAgentHandlers() {
220
- agent.removeAllListeners('event');
221
- agent.on('event', (event) => {
222
- switch (event.type) {
223
- case 'thinking':
224
- if (!hasResponse) {
225
- ui.thinking(event.data.message || 'Analyzing your request...');
226
- SessionBridge.onThinking(event.data.message || 'Analyzing your request...');
227
- }
228
- break;
229
- // ── Streaming ──
230
- case 'stream_start':
231
- ui.startAssistantResponse(event.data.persona);
232
- SessionBridge.onStreamStart(event.data.persona);
233
- hasResponse = true;
234
- streamBuffer = '';
235
- break;
236
- case 'stream_text':
237
- ui.streamText(event.data.text);
238
- SessionBridge.onStreamText(event.data.text);
239
- streamBuffer += event.data.text;
240
- break;
241
- case 'stream_end':
242
- ui.endAssistantResponse();
243
- SessionBridge.onStreamEnd();
244
- // Parse planner output tags from accumulated stream
245
- if (streamBuffer) {
246
- parsePlannerOutput(streamBuffer);
247
- streamBuffer = '';
248
- }
249
- break;
250
- // ── Non-streaming fallback ──
251
- case 'response':
252
- if (!hasResponse) {
253
- ui.response(event.data.text, event.data.persona);
254
- SessionBridge.onAssistantMessage(event.data.text, event.data.persona);
255
- hasResponse = true;
256
- }
257
- break;
258
- // ── Tools ──
259
- case 'tool_call':
260
- if (enableTools) {
261
- ui.toolCall(event.data.name, event.data.input);
262
- SessionBridge.onToolCall(event.data.name, event.data.input);
263
- }
264
- break;
265
- case 'tool_result':
266
- if (enableTools) {
267
- ui.toolResult(event.data.name, event.data.result, event.data.success);
268
- SessionBridge.onToolResult(event.data.name, event.data.result, event.data.success);
269
- const r = event.data.result;
270
- if (r?.success && event.data.name === 'write_file') {
271
- ui.fileChanged('created', r.path, r.lines ? `${r.lines} lines` : undefined);
272
- }
273
- else if (r?.success && event.data.name === 'edit_file') {
274
- ui.fileChanged('modified', r.path || '', r.linesChanged ? `${r.linesChanged} lines` : undefined);
275
- }
276
- else if (r?.success && event.data.name === 'edit_lines') {
277
- ui.fileChanged('modified', r.path || '', r.linesChanged ? `${r.linesChanged} lines` : undefined);
278
- }
279
- else if (r?.success && event.data.name === 'verified_edit') {
280
- ui.fileChanged('modified', r.path || '', r.linesChanged ? `${r.linesChanged} lines` : undefined);
281
- }
282
- // Diff preview: show colorized diff when available
283
- if (r?.diff && (event.data.name === 'edit_file' || event.data.name === 'edit_lines' || event.data.name === 'verified_edit')) {
284
- const diffKey = r.path || '';
285
- if (diffKey)
286
- lastDiffByPath.set(diffKey, r.diff);
287
- // Cap how many diff previews we print per assistant turn to reduce spam.
288
- const maxDiffPreviews = condensedUI ? 2 : 4;
289
- if (diffPreviewCount < maxDiffPreviews) {
290
- ui.showDiff(r.diff, r.path || r.message || '', {
291
- maxHunks: condensedUI ? 2 : 4,
292
- maxLines: condensedUI ? 35 : 70,
293
- });
294
- diffPreviewCount++;
295
- }
296
- }
297
- }
298
- break;
299
- // ── Iteration ──
300
- case 'iteration':
301
- if (!hasResponse && event.data?.current) {
302
- ui.updateThinking(`Thinking... step ${event.data.current}`);
303
- }
304
- hasResponse = false;
305
- break;
306
- // ── Errors / Warnings ──
307
- case 'error':
308
- ui.error(event.data.message || event.data.error);
309
- SessionBridge.onError(event.data.message || event.data.error);
310
- break;
311
- case 'warning':
312
- ui.warning(event.data.message);
313
- break;
314
- case 'mode_changed':
315
- currentMode = event.data.to;
316
- ui.info(`Mode: ${currentMode}`);
317
- SessionBridge.updateState({ mode: currentMode });
318
- break;
319
- }
320
- });
321
- }
322
- let hasResponse = false;
323
- // Used by the /diff command to show the latest assistant-turn diffs.
324
- let lastDiffByPath = new Map();
325
- let diffPreviewCount = 0;
326
- setupAgentHandlers();
327
- // ── Session bootstrap ──────────────────────────────────
328
- let currentSession;
329
- const existingId = options.session;
330
- if (existingId) {
331
- const loaded = await sessionManager.loadSession(existingId);
332
- if (loaded) {
333
- currentSession = loaded;
334
- agent.setMessages(loaded.messages || []);
335
- }
336
- else {
337
- currentSession = await sessionManager.createSession({ model, cwd: process.cwd() });
338
- }
339
- }
340
- else {
341
- currentSession = await sessionManager.createSession({ model, cwd: process.cwd() });
342
- }
343
- // Update SessionBridge with initial state
344
- SessionBridge.updateState({
345
- sessionId: currentSession.id,
346
- model,
347
- mode: currentMode,
348
- messages: agent.getMessages(),
349
- isProcessing: false,
350
- });
351
- const inputQueue = [];
352
- let inputResolver = null;
353
- let isAgentRunning = false;
354
- let rl = null;
355
- let sigintCount = 0;
356
- let lastSigintTime = 0;
357
- function handleSigint() {
358
- const now = Date.now();
359
- // debounce in case both rl and keypress emit SIGINT
360
- if (now - lastSigintTime < 100)
361
- return;
362
- lastSigintTime = now;
363
- if (sigintCount === 0) {
364
- console.log('');
365
- ui.warning('Press Ctrl+C again to exit.');
366
- if (!isAgentRunning)
367
- promptUser();
368
- sigintCount++;
369
- setTimeout(() => { sigintCount = 0; }, 3000);
370
- }
371
- else {
372
- process.exit(0);
373
- }
374
- }
375
- function promptUser() {
376
- process.stdout.write(chalk.hex('#00E676').bold('❯ You ') + '');
377
- }
378
- function createRL() {
379
- if (rl)
380
- return;
381
- rl = readline.createInterface({
382
- input: process.stdin,
383
- output: process.stdout,
384
- terminal: true
385
- });
386
- rl.on('line', (line) => {
387
- const msg = line.trim();
388
- if (!msg) {
389
- if (!isAgentRunning)
390
- promptUser();
391
- return;
392
- }
393
- if (isAgentRunning) {
394
- agent.injectMessage(msg);
395
- console.log('');
396
- ui.success(`Message queued for agent's next step.`);
397
- }
398
- else {
399
- const item = { message: msg, source: 'tui' };
400
- if (inputResolver) {
401
- inputResolver(item);
402
- }
403
- else {
404
- inputQueue.push(item);
405
- }
406
- }
407
- });
408
- rl.on('SIGINT', handleSigint);
409
- }
410
- function closeRL() {
411
- if (rl) {
412
- rl.close();
413
- rl = null;
414
- }
415
- }
416
- // Listen for messages from WebUI
417
- SessionBridge.on('user_message', async (content, source) => {
418
- if (source === 'webui') {
419
- if (isAgentRunning) {
420
- agent.injectMessage(content);
421
- console.log('');
422
- ui.info(`WebUI message injected into agent's thought process.`);
423
- }
424
- else {
425
- const item = { message: content, source: 'webui' };
426
- if (inputResolver) {
427
- inputResolver(item);
428
- }
429
- else {
430
- inputQueue.push(item);
431
- }
432
- }
433
- }
434
- });
435
- /**
436
- * Get input from either TUI or WebUI (whichever comes first)
437
- */
438
- async function getInput() {
439
- createRL();
440
- if (inputQueue.length > 0) {
441
- return inputQueue.shift();
442
- }
443
- return new Promise((resolve) => {
444
- promptUser();
445
- inputResolver = (input) => {
446
- inputResolver = null;
447
- resolve(input);
448
- };
449
- });
450
- }
451
- async function showPathSuggestions(raw) {
452
- const input = raw.trim().slice(1).trim(); // drop leading '@'
453
- const target = input ? path.resolve(process.cwd(), input) : process.cwd();
454
- try {
455
- const stats = await fs.stat(target);
456
- const dir = stats.isDirectory() ? target : path.dirname(target);
457
- const base = stats.isDirectory() ? '' : path.basename(target);
458
- const entries = await fs.readdir(dir, { withFileTypes: true });
459
- const filtered = base
460
- ? entries.filter(e => e.name.toLowerCase().startsWith(base.toLowerCase()))
461
- : entries;
462
- if (!filtered.length) {
463
- ui.info(`No matches under ${dir}`);
464
- return;
465
- }
466
- console.log('');
467
- console.log(' ' + chalk.bold('Files & folders:'));
468
- for (const entry of filtered.slice(0, 50)) {
469
- const isDir = entry.isDirectory();
470
- const name = entry.name + (isDir ? '/' : '');
471
- const rel = path.relative(process.cwd(), path.join(dir, entry.name)) || '.';
472
- console.log(' ' +
473
- (isDir ? chalk.hex('#40C4FF')('📁') : chalk.hex('#CE93D8')('📄')) +
474
- ' ' +
475
- chalk.white(name) +
476
- chalk.hex('#6B6B7B')(` · ${rel}`));
477
- }
478
- if (filtered.length > 50) {
479
- console.log(' ' + chalk.hex('#6B6B7B')(`… and ${filtered.length - 50} more`));
480
- }
481
- console.log('');
482
- }
483
- catch (error) {
484
- ui.error('Failed to list files for @ path', error);
485
- }
486
- }
487
- function showSlashHelp() {
488
- console.log('');
489
- console.log(chalk.bold(' XibeCode chat commands'));
490
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
491
- console.log(' ' + chalk.hex('#00D4FF')('/help') + chalk.hex('#6B6B7B')(' show this help, not an AI reply'));
492
- console.log(' ' + chalk.hex('#00D4FF')('/mcp') + chalk.hex('#6B6B7B')(' show connected MCP servers and tools'));
493
- console.log(' ' + chalk.hex('#00D4FF')('/new') + chalk.hex('#6B6B7B')(' start a new chat session'));
494
- console.log(' ' + chalk.hex('#00D4FF')('/sessions') + chalk.hex('#6B6B7B')(' list and switch saved sessions'));
495
- console.log(' ' + chalk.hex('#00D4FF')('/models') + chalk.hex('#6B6B7B')(' show/switch models'));
496
- console.log(' ' + chalk.hex('#00D4FF')('/provider') + chalk.hex('#6B6B7B')(' switch between Anthropic/OpenAI format'));
497
- console.log(' ' + chalk.hex('#00D4FF')('/format <claude|openai>') + chalk.hex('#6B6B7B')(' quick alias to set provider'));
498
- console.log(' ' + chalk.hex('#00D4FF')('/export') + chalk.hex('#6B6B7B')(' export this session to Markdown'));
499
- console.log(' ' + chalk.hex('#00D4FF')('/plan') + chalk.hex('#6B6B7B')(' create or update todo.md from a high-level goal'));
500
- console.log(' ' + chalk.hex('#00D4FF')('/compact') + chalk.hex('#6B6B7B')(' compact long conversation history'));
501
- console.log(' ' + chalk.hex('#00D4FF')('/diff <path>') + chalk.hex('#6B6B7B')(' show stored diff from last edit (by path)'));
502
- console.log(' ' + chalk.hex('#00D4FF')('/details') + chalk.hex('#6B6B7B')(' toggle verbose tool details'));
503
- console.log(' ' + chalk.hex('#00D4FF')('/thinking') + chalk.hex('#6B6B7B')(' toggle thinking spinner'));
504
- console.log(' ' + chalk.hex('#00D4FF')('/themes') + chalk.hex('#6B6B7B')(' choose a color theme'));
505
- console.log(' ' + chalk.hex('#00D4FF')('/ui condensed') + chalk.hex('#6B6B7B')(' toggle condensed UI (less spam)'));
506
- console.log(' ' + chalk.hex('#00D4FF')('@path') + chalk.hex('#6B6B7B')(' list files/folders under path (or cwd if just "@")'));
507
- console.log(' ' + chalk.hex('#00D4FF')('/undo') + chalk.hex('#6B6B7B')(' undo last AI turn (restore previous conversation state)'));
508
- console.log(' ' + chalk.hex('#00D4FF')('/redo') + chalk.hex('#6B6B7B')(' redo undone turn'));
509
- console.log(' ' + chalk.hex('#00D4FF')('/cost') + chalk.hex('#6B6B7B')(' show token usage and estimated cost'));
510
- console.log(' ' + chalk.hex('#00D4FF')('/skill <name>') + chalk.hex('#6B6B7B')(' activate a skill (e.g. /skill refactor-clean-code)'));
511
- console.log(' ' + chalk.hex('#00D4FF')('/skill list') + chalk.hex('#6B6B7B')(' show available skills'));
512
- console.log(' ' + chalk.hex('#00D4FF')('/skill off') + chalk.hex('#6B6B7B')(' deactivate current skill'));
513
- console.log(' ' + chalk.hex('#00D4FF')('/learn <name> <url>') + chalk.hex('#6B6B7B')(' learn a new skill from docs URL'));
514
- console.log(' ' + chalk.hex('#00D4FF')('/marketplace [query]') + chalk.hex('#6B6B7B')(' search & install skills from marketplace'));
515
- console.log(' ' + chalk.hex('#00D4FF')('/skills-sh [query]') + chalk.hex('#6B6B7B')(' search skills from skills.sh'));
516
- console.log(' ' + chalk.hex('#00D4FF')('/team') + chalk.hex('#6B6B7B')(' activate Team Mode (Arya & Co.)'));
517
- console.log(' ' + chalk.hex('#00D4FF')('clear') + chalk.hex('#6B6B7B')(' clear screen and redraw header'));
518
- console.log(' ' + chalk.hex('#00D4FF')('tools on/off') + chalk.hex('#6B6B7B')(' toggle tools (editor & filesystem)'));
519
- console.log(' ' + chalk.hex('#00D4FF')('exit / quit') + chalk.hex('#6B6B7B')(' end the chat session'));
520
- console.log(' ' + chalk.hex('#00D4FF')('!cmd') + chalk.hex('#6B6B7B')(' run a shell command and feed output to AI'));
521
- console.log('');
522
- }
523
- async function handleShellBang(input) {
524
- const cmd = input.slice(1).trim();
525
- if (!cmd) {
526
- ui.warning('No command provided after "!". Example: !ls -la');
527
- return;
528
- }
529
- ui.info(`Running shell command: ${cmd}`);
530
- const result = await toolExecutor.execute('run_command', { command: cmd, cwd: process.cwd(), timeout: 300 });
531
- const stdout = result.stdout || '';
532
- const stderr = result.stderr || '';
533
- console.log('');
534
- console.log(chalk.bold(' Command Output'));
535
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
536
- if (stdout) {
537
- console.log(chalk.white(stdout));
538
- }
539
- if (stderr) {
540
- console.log(chalk.red(stderr));
541
- }
542
- console.log('');
543
- const summary = [
544
- `Shell command: ${cmd}`,
545
- '',
546
- stdout ? stdout : '',
547
- stderr ? `STDERR:\n${stderr}` : '',
548
- ].join('\n');
549
- const tools = enableTools ? toolExecutor.getTools() : [];
550
- await agent.run(summary, tools, toolExecutor);
551
- const stats = agent.getStats();
552
- if (config.isStatusBarEnabled()) {
553
- ui.renderStatusBar({
554
- model,
555
- sessionTitle: currentSession.title,
556
- cwd: process.cwd(),
557
- toolsEnabled: enableTools,
558
- themeName: ui.getThemeName(),
559
- mode: currentMode,
560
- });
561
- }
562
- await sessionManager.saveMessagesAndStats({
563
- id: currentSession.id,
564
- messages: agent.getMessages(),
565
- stats,
566
- });
567
- }
568
- async function handleSessionsCommand() {
569
- const sessions = await sessionManager.listSessions();
570
- if (sessions.length === 0) {
571
- ui.info('No saved sessions yet.');
572
- return;
573
- }
574
- closeRL();
575
- const { picked } = await inquirer.prompt([
576
- {
577
- type: 'list',
578
- name: 'picked',
579
- message: 'Select session',
580
- choices: sessions.map(s => ({
581
- name: `${s.title} · ${s.model} · ${s.updated}`,
582
- value: s.id,
583
- })),
584
- },
585
- ]);
586
- createRL();
587
- const loaded = await sessionManager.loadSession(picked);
588
- if (!loaded) {
589
- ui.error('Failed to load selected session');
590
- return;
591
- }
592
- currentSession = loaded;
593
- agent.setMessages(loaded.messages || []);
594
- ui.success(`Switched to session ${loaded.title}`);
595
- }
596
- async function handleNewSession() {
597
- currentSession = await sessionManager.createSession({ model, cwd: process.cwd() });
598
- agent = new EnhancedAgent({
599
- apiKey: apiKey,
600
- baseUrl,
601
- model,
602
- maxIterations: 150,
603
- verbose: false,
604
- provider: currentProvider || config.get('provider'),
605
- customProviderFormat: config.get('customProviderFormat'),
606
- defaultSkillsPrompt,
607
- }, currentProvider || config.get('provider'));
608
- setupAgentHandlers();
609
- currentMode = agent.getMode();
610
- ui.success('Started new session');
611
- }
612
- async function handleModelsCommand() {
613
- const current = model;
614
- const fixedModels = [
615
- 'claude-sonnet-4-5-20250929',
616
- 'claude-opus-4-5-20251101',
617
- 'claude-haiku-4-5-20251015',
618
- ];
619
- const customModels = (config.get('customModels') || []);
620
- const unique = Array.from(new Set([current, ...fixedModels, ...customModels.map(m => m.id)]));
621
- closeRL();
622
- const { picked } = await inquirer.prompt([
623
- {
624
- type: 'list',
625
- name: 'picked',
626
- message: 'Select model',
627
- choices: unique.map(m => {
628
- const cm = customModels.find(x => x.id === m);
629
- const labelBase = cm ? `${m} (${cm.provider})` : m;
630
- const name = m === current ? `${labelBase} (current)` : labelBase;
631
- return { name, value: m };
632
- }),
633
- },
634
- ]);
635
- createRL();
636
- config.set('model', picked);
637
- ui.success(`Model set to: ${picked}`);
638
- }
639
- async function handleThemesCommand() {
640
- const current = ui.getThemeName();
641
- closeRL();
642
- const { picked } = await inquirer.prompt([
643
- {
644
- type: 'list',
645
- name: 'picked',
646
- message: 'Select theme',
647
- choices: THEME_NAMES.map(name => ({
648
- name: name === current ? `${name} (current)` : name,
649
- value: name,
650
- })),
651
- },
652
- ]);
653
- createRL();
654
- ui.setTheme(picked);
655
- config.set('theme', picked);
656
- ui.success(`Theme set to: ${picked}`);
657
- }
658
- async function handleExportCommand() {
659
- const session = {
660
- ...currentSession,
661
- messages: agent.getMessages(),
662
- };
663
- const markdown = exportSessionToMarkdown(session);
664
- const exportsDir = path.join(config['getConfigPath'], '..', 'sessions');
665
- const fileName = `${session.id}.md`;
666
- const fullPath = path.join(exportsDir, fileName);
667
- await fs.mkdir(exportsDir, { recursive: true });
668
- await fs.writeFile(fullPath, markdown, 'utf-8');
669
- ui.success(`Session exported to ${fullPath}`);
670
- }
671
- async function handlePlanCommand(raw) {
672
- const description = raw.replace(/^\/plan\s*/i, '').trim();
673
- if (!description) {
674
- ui.info('Usage: /plan your high-level goal here');
675
- return;
676
- }
677
- const result = await planMode.buildPlan(description);
678
- const todoManager = new TodoManager(process.cwd());
679
- const next = todoManager.getNextPending(result.doc);
680
- ui.success(`Created/updated todo.md with ${result.tasks.length} task(s).`);
681
- if (next) {
682
- ui.info(`Next TODO [id:${next.id}]: ${next.title}`);
683
- }
684
- }
685
- async function handleCompactCommand() {
686
- const messages = agent.getMessages();
687
- if (messages.length <= 10) {
688
- ui.info('Conversation is short; no compaction needed.');
689
- return;
690
- }
691
- const preserved = messages.slice(-6);
692
- const summaryMessage = {
693
- role: 'assistant',
694
- content: 'Earlier conversation has been compacted to save context. Key details from the last messages are preserved.',
695
- };
696
- const compacted = [summaryMessage, ...preserved];
697
- agent.setMessages(compacted);
698
- await sessionManager.saveMessagesAndStats({
699
- id: currentSession.id,
700
- messages: compacted,
701
- stats: agent.getStats(),
702
- });
703
- ui.success('Conversation compacted.');
704
- }
705
- async function handleAtPathFuzzy(raw) {
706
- const input = raw.trim().slice(1).trim();
707
- const pattern = input ? `**/*${input}*` : '**/*';
708
- try {
709
- const files = await contextManager.searchFiles(pattern, { maxResults: 100 });
710
- if (!files.length) {
711
- ui.info(`No matches for pattern ${pattern}`);
712
- return;
713
- }
714
- console.log('');
715
- console.log(' ' + chalk.bold('Files'));
716
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
717
- files.forEach(f => {
718
- console.log(' ' + chalk.hex('#CE93D8')('📄') + ' ' + chalk.white(f));
719
- });
720
- console.log('');
721
- }
722
- catch (error) {
723
- ui.error('Failed to search files for @ path', error);
724
- }
725
- }
726
- async function handleTeamCommand() {
727
- ui.info('Activating Team Mode...');
728
- if (typeof agent.setModeFromUser === 'function') {
729
- agent.setModeFromUser('team_leader', 'User activated Team Mode via /team');
730
- }
731
- currentMode = 'team_leader';
732
- console.log('');
733
- console.log(chalk.bold.hex('#FFD600')(' 👑 Team Mode Activated'));
734
- console.log(chalk.hex('#6B6B7B')(' ────────────────────────────────'));
735
- console.log(' Arya (Team Leader) is now coordinating the team:');
736
- console.log(' · ' + chalk.hex('#00B0FF')('Siri (SEO)') + ' · ' + chalk.hex('#FF6D00')('Agni (Product)'));
737
- console.log(' · ' + chalk.hex('#7C4DFF')('Anna (Arch)') + ' · ' + chalk.hex('#00E676')('Alex (Eng)'));
738
- console.log(' · ' + chalk.hex('#00BCD4')('David (Data)') + ' · ' + chalk.hex('#E91E63')('Sanvi (Rsrch)'));
739
- console.log('');
740
- console.log(' You can ask Arya to lead, or call agents directly:');
741
- console.log(' ' + chalk.dim('> @Agni create user stories for login'));
742
- console.log(' ' + chalk.dim('> @Siri check seo for landing page'));
743
- console.log('');
744
- if (config.isStatusBarEnabled()) {
745
- ui.renderStatusBar({
746
- model,
747
- sessionTitle: currentSession.title,
748
- cwd: process.cwd(),
749
- toolsEnabled: enableTools,
750
- themeName: ui.getThemeName(),
751
- mode: currentMode,
752
- });
753
- }
754
- }
755
- // ── Global key handler for mode cycling (Tab) ───────────
756
- if (process.stdin.isTTY) {
757
- readline.emitKeypressEvents(process.stdin);
758
- if (typeof process.stdin.setRawMode === 'function') {
759
- try {
760
- process.stdin.setRawMode(true);
761
- }
762
- catch {
763
- // ignore if raw mode cannot be set (e.g. non‑TTY env)
764
- }
765
- }
766
- process.stdin.on('keypress', (_str, key) => {
767
- if (!key)
768
- return;
769
- if (key.ctrl && key.name === 'c') {
770
- handleSigint();
771
- return;
772
- }
773
- if (key.ctrl && key.name === 'k') {
774
- condensedUI = !condensedUI;
775
- ui.setCondensedUI(condensedUI);
776
- ui.success(`Condensed UI ${condensedUI ? 'enabled' : 'disabled'} (Ctrl+K)`);
777
- return;
778
- }
779
- if (key.name === 'tab') {
780
- const idx = allModes.indexOf(currentMode);
781
- const next = allModes[(idx + 1) % allModes.length];
782
- currentMode = next;
783
- if (typeof agent.setModeFromUser === 'function') {
784
- agent.setModeFromUser(next, 'User pressed Tab to cycle mode');
785
- }
786
- ui.info(`Mode: ${currentMode} (press Tab to cycle)`);
787
- if (config.isStatusBarEnabled()) {
788
- ui.renderStatusBar({
789
- model,
790
- sessionTitle: currentSession.title,
791
- cwd: process.cwd(),
792
- toolsEnabled: enableTools,
793
- themeName: ui.getThemeName(),
794
- mode: currentMode,
795
- });
796
- }
797
- }
798
- });
799
- }
800
- // ── Chat loop ──
801
- while (true) {
802
- // Get input from either TUI or WebUI
803
- const input = await getInput();
804
- let message = input.message;
805
- const messageSource = input.source; // Track where the message came from
806
- // Show indicator for WebUI messages
807
- if (messageSource === 'webui') {
808
- console.log(chalk.hex('#00D4FF').bold('❯ WebUI ') + chalk.white(message));
809
- }
810
- // Special interactive flow when user types just "@"
811
- if (message.trim() === '@') {
812
- try {
813
- const dir = process.cwd();
814
- const entries = await fs.readdir(dir, { withFileTypes: true });
815
- if (!entries.length) {
816
- ui.info('No files or folders in current directory');
817
- continue;
818
- }
819
- const choices = entries.slice(0, 100).map(entry => {
820
- const isDir = entry.isDirectory();
821
- const label = (isDir ? '📁 ' : '📄 ') +
822
- entry.name +
823
- (isDir ? '/' : '');
824
- return {
825
- name: label,
826
- value: entry.name + (isDir ? '/' : ''),
827
- };
828
- });
829
- closeRL();
830
- const { picked } = await inquirer.prompt([
831
- {
832
- type: 'list',
833
- name: 'picked',
834
- message: 'Select file or folder',
835
- choices,
836
- },
837
- ]);
838
- const followUp = await inquirer.prompt([
839
- {
840
- type: 'input',
841
- name: 'message',
842
- message: chalk.hex('#00E676').bold('❯ You '),
843
- prefix: '',
844
- default: '@' + picked,
845
- },
846
- ]);
847
- createRL();
848
- message = followUp.message;
849
- }
850
- catch (error) {
851
- createRL();
852
- ui.error('Failed to list files for selection', error);
853
- continue;
854
- }
855
- }
856
- if (!message.trim())
857
- continue;
858
- const trimmed = message.trim();
859
- const lowerMessage = trimmed.toLowerCase();
860
- if (lowerMessage === '/help') {
861
- showSlashHelp();
862
- continue;
863
- }
864
- if (lowerMessage === '/diff' || lowerMessage.startsWith('/diff ')) {
865
- const requested = trimmed.slice('/diff'.length).trim();
866
- if (!requested) {
867
- ui.warning('Usage: /diff <filePath>');
868
- continue;
869
- }
870
- // Try exact match first, then fall back to suffix (common for relative paths).
871
- let diff = lastDiffByPath.get(requested);
872
- let fileLabel = requested;
873
- if (!diff) {
874
- let foundKey;
875
- for (const key of lastDiffByPath.keys()) {
876
- if (key === requested || key.endsWith(requested) || requested.endsWith(key)) {
877
- foundKey = key;
878
- break;
879
- }
880
- }
881
- if (foundKey) {
882
- diff = lastDiffByPath.get(foundKey);
883
- fileLabel = foundKey;
884
- }
885
- }
886
- if (!diff) {
887
- const keys = [...lastDiffByPath.keys()].slice(0, 8);
888
- if (keys.length === 0) {
889
- ui.warning('No diff stored for this turn yet. Try editing a file first.');
890
- }
891
- else {
892
- ui.warning(`No stored diff for "${requested}". Available: ${keys.join(', ')}`);
893
- }
894
- continue;
895
- }
896
- ui.showDiff(diff, fileLabel, {
897
- maxHunks: condensedUI ? 10 : 16,
898
- maxLines: condensedUI ? 220 : 340,
899
- });
900
- continue;
901
- }
902
- if (lowerMessage.startsWith('/skills-sh')) {
903
- const parts = trimmed.split(/\s+/);
904
- const subcommand = parts[1]?.toLowerCase();
905
- if (!subcommand) {
906
- ui.info('Usage: /skills-sh <query> OR /skills-sh install <id>');
907
- continue;
908
- }
909
- if (subcommand === 'install') {
910
- const skillId = parts.slice(2).join(' ');
911
- if (!skillId) {
912
- ui.error('Please provide a skill ID: /skills-sh install <id>');
913
- continue;
914
- }
915
- ui.info(`Installing skill "${skillId}" from skills.sh...`);
916
- const result = await skillManager.installFromSkillsSh(skillId);
917
- if (result.success) {
918
- ui.success(result.message || 'Skill installed successfully');
919
- }
920
- else {
921
- ui.error(`Failed to install skill: ${result.message}`);
922
- }
923
- }
924
- else {
925
- // Search
926
- const query = trimmed.replace(/^\/skills-sh\s*/i, '').trim();
927
- ui.info(`Searching skills.sh for "${query}"...`);
928
- const results = await skillManager.searchSkillsSh(query);
929
- if (results.length === 0) {
930
- ui.info('No skills found on skills.sh');
931
- }
932
- else {
933
- console.log('');
934
- console.log(chalk.bold(' skills.sh Results'));
935
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────────'));
936
- results.forEach(r => {
937
- console.log(' ' + chalk.hex('#00D4FF')(r.id));
938
- if (r.url)
939
- console.log(' ' + chalk.hex('#6B6B7B')(r.url));
940
- console.log('');
941
- });
942
- console.log(' ' + chalk.dim('To install: /skills-sh install <id>'));
943
- console.log('');
944
- }
945
- }
946
- continue;
947
- }
948
- if (lowerMessage === '/new') {
949
- await handleNewSession();
950
- continue;
951
- }
952
- if (lowerMessage === '/sessions') {
953
- await handleSessionsCommand();
954
- continue;
955
- }
956
- if (lowerMessage === '/models') {
957
- await handleModelsCommand();
958
- continue;
959
- }
960
- if (lowerMessage === '/provider') {
961
- closeRL();
962
- const { picked } = await inquirer.prompt([
963
- {
964
- type: 'list',
965
- name: 'picked',
966
- message: 'Select provider / API format',
967
- choices: [
968
- { name: 'Anthropic format (Claude / Messages API)', value: 'anthropic' },
969
- { name: 'OpenAI-compatible format (chat/completions)', value: 'openai' },
970
- ],
971
- },
972
- ]);
973
- createRL();
974
- currentProvider = picked;
975
- config.set('provider', picked);
976
- // Recreate agent with new provider but keep conversation history
977
- const previousMessages = agent.getMessages();
978
- agent = new EnhancedAgent({
979
- apiKey: apiKey,
980
- baseUrl: config.getBaseUrl() || baseUrl,
981
- model,
982
- maxIterations: 150,
983
- verbose: false,
984
- defaultSkillsPrompt,
985
- }, currentProvider);
986
- agent.setMessages(previousMessages);
987
- setupAgentHandlers();
988
- currentMode = agent.getMode();
989
- ui.success(`Provider set to: ${picked}`);
990
- continue;
991
- }
992
- if (lowerMessage.startsWith('/format')) {
993
- const parts = trimmed.split(/\s+/);
994
- const arg = (parts[1] || '').toLowerCase();
995
- if (!arg) {
996
- ui.info('Usage: /format claude or /format openai');
997
- continue;
998
- }
999
- let pickedProvider = null;
1000
- if (arg === 'claude' || arg === 'anthropic') {
1001
- pickedProvider = 'anthropic';
1002
- }
1003
- else if (arg === 'openai') {
1004
- pickedProvider = 'openai';
1005
- }
1006
- else {
1007
- ui.warning('Unknown format. Use "claude" or "openai".');
1008
- continue;
1009
- }
1010
- currentProvider = pickedProvider;
1011
- config.set('provider', pickedProvider);
1012
- // Recreate agent with new provider but keep conversation history
1013
- const previousMessages = agent.getMessages();
1014
- agent = new EnhancedAgent({
1015
- apiKey: apiKey,
1016
- baseUrl: config.getBaseUrl() || baseUrl,
1017
- model,
1018
- maxIterations: 150,
1019
- verbose: false,
1020
- defaultSkillsPrompt,
1021
- }, currentProvider);
1022
- agent.setMessages(previousMessages);
1023
- setupAgentHandlers();
1024
- currentMode = agent.getMode();
1025
- ui.success(`Provider/format set via /format: ${pickedProvider}`);
1026
- continue;
1027
- }
1028
- if (lowerMessage.startsWith('/plan')) {
1029
- await handlePlanCommand(trimmed);
1030
- continue;
1031
- }
1032
- if (lowerMessage === '/themes') {
1033
- await handleThemesCommand();
1034
- continue;
1035
- }
1036
- if (lowerMessage === '/export') {
1037
- await handleExportCommand();
1038
- continue;
1039
- }
1040
- if (lowerMessage === '/compact') {
1041
- await handleCompactCommand();
1042
- continue;
1043
- }
1044
- if (lowerMessage === '/team') {
1045
- await handleTeamCommand();
1046
- continue;
1047
- }
1048
- if (lowerMessage === '/undo') {
1049
- if (undoStack.length === 0) {
1050
- ui.info('Nothing to undo.');
1051
- }
1052
- else {
1053
- const current = { messages: [...agent.getMessages()], label: 'redo point' };
1054
- redoStack.push(current);
1055
- const prev = undoStack.pop();
1056
- agent.setMessages(prev.messages);
1057
- await sessionManager.saveMessagesAndStats({ id: currentSession.id, messages: prev.messages, stats: agent.getStats() });
1058
- ui.success(`Undo: reverted to previous state (${prev.label})`);
1059
- }
1060
- continue;
1061
- }
1062
- if (lowerMessage === '/redo') {
1063
- if (redoStack.length === 0) {
1064
- ui.info('Nothing to redo.');
1065
- }
1066
- else {
1067
- const current = { messages: [...agent.getMessages()], label: 'undo point' };
1068
- undoStack.push(current);
1069
- const next = redoStack.pop();
1070
- agent.setMessages(next.messages);
1071
- await sessionManager.saveMessagesAndStats({ id: currentSession.id, messages: next.messages, stats: agent.getStats() });
1072
- ui.success(`Redo: restored next state`);
1073
- }
1074
- continue;
1075
- }
1076
- if (lowerMessage === '/cost') {
1077
- const stats = agent.getStats();
1078
- console.log('');
1079
- console.log(chalk.bold(' Token Usage & Cost'));
1080
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
1081
- console.log(' ' + chalk.hex('#00D4FF')('Input tokens: ') + chalk.white(stats.inputTokens.toLocaleString()));
1082
- console.log(' ' + chalk.hex('#00D4FF')('Output tokens: ') + chalk.white(stats.outputTokens.toLocaleString()));
1083
- console.log(' ' + chalk.hex('#00D4FF')('Total tokens: ') + chalk.white(stats.totalTokens.toLocaleString()));
1084
- if (stats.costLabel) {
1085
- console.log(' ' + chalk.hex('#00D4FF')('Est. cost: ') + chalk.hex('#00E676')(stats.costLabel));
1086
- }
1087
- else {
1088
- console.log(' ' + chalk.hex('#6B6B7B')(' (cost tracking unavailable for this model)'));
1089
- }
1090
- console.log('');
1091
- continue;
1092
- }
1093
- if (lowerMessage.startsWith('/skill')) {
1094
- const parts = trimmed.split(/\s+/);
1095
- const subcommand = parts[1]?.toLowerCase();
1096
- if (!subcommand || subcommand === 'list') {
1097
- const skills = skillManager.listSkills();
1098
- console.log('');
1099
- console.log(chalk.bold(' Available Skills'));
1100
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────────'));
1101
- if (skills.length === 0) {
1102
- console.log(' ' + chalk.hex('#6B6B7B')('No skills found'));
1103
- }
1104
- else {
1105
- skills.forEach(skill => {
1106
- const active = agent.getActiveSkill() === skill.name ? chalk.hex('#00E676')(' (active)') : '';
1107
- console.log(' ' + chalk.hex('#00D4FF')(skill.name) + active);
1108
- console.log(' ' + chalk.hex('#6B6B7B')(skill.description));
1109
- if (skill.tags && skill.tags.length > 0) {
1110
- console.log(' ' + chalk.dim(`tags: ${skill.tags.join(', ')}`));
1111
- }
1112
- console.log('');
1113
- });
1114
- }
1115
- console.log(' ' + chalk.dim('Usage: /skill <name> to activate'));
1116
- console.log(' ' + chalk.dim('Tip: /marketplace to browse community skills'));
1117
- console.log('');
1118
- continue;
1119
- }
1120
- if (subcommand === 'off') {
1121
- agent.setSkill(null);
1122
- ui.success('Skill deactivated');
1123
- continue;
1124
- }
1125
- // Activate skill
1126
- const skillName = parts.slice(1).join('-');
1127
- const skill = skillManager.getSkill(skillName);
1128
- if (!skill) {
1129
- ui.error(`Skill not found: ${skillName}`);
1130
- console.log(' ' + chalk.dim('Use /skill list to see available skills'));
1131
- continue;
1132
- }
1133
- agent.setSkill(skill.name, skill.instructions);
1134
- ui.success(`Activated skill: ${skill.name}`);
1135
- console.log(' ' + chalk.hex('#6B6B7B')(skill.description));
1136
- continue;
1137
- }
1138
- if (lowerMessage.startsWith('/learn')) {
1139
- const parts = trimmed.split(/\s+/);
1140
- if (parts.length < 3) {
1141
- ui.error('Usage: /learn <skill-name> <docs-url>');
1142
- console.log(' ' + chalk.dim('Example: /learn nextjs https://nextjs.org/docs'));
1143
- continue;
1144
- }
1145
- const skillName = parts[1];
1146
- const docsUrl = parts[2];
1147
- // Validate URL
1148
- try {
1149
- new URL(docsUrl);
1150
- }
1151
- catch {
1152
- ui.error('Invalid URL. Please provide a valid documentation URL.');
1153
- continue;
1154
- }
1155
- console.log('');
1156
- console.log(chalk.hex('#00D4FF')(' 📚 Learning from docs: ') + chalk.white(docsUrl));
1157
- console.log(chalk.dim(' This may take a moment...'));
1158
- console.log('');
1159
- const result = await skillManager.learnFromDocs(skillName, docsUrl, 60, (msg) => {
1160
- console.log(' ' + chalk.hex('#6B6B7B')(` ${msg}`));
1161
- });
1162
- if (result.success) {
1163
- console.log('');
1164
- ui.success(`Learned skill "${skillName}" from ${result.pagesScraped} pages`);
1165
- console.log(' ' + chalk.hex('#6B6B7B')(`Saved to: ${result.filePath}`));
1166
- if (result.marketplaceId) {
1167
- console.log(' ' + chalk.hex('#00E676')(`✓ Published to Skills Marketplace`));
1168
- }
1169
- console.log(' ' + chalk.dim(`Activate with: /skill ${skillName}`));
1170
- console.log('');
1171
- }
1172
- else {
1173
- ui.error(`Failed to learn from docs: ${result.error}`);
1174
- }
1175
- continue;
1176
- }
1177
- if (lowerMessage.startsWith('/marketplace')) {
1178
- const query = trimmed.replace(/^\/marketplace\s*/i, '').trim();
1179
- console.log('');
1180
- console.log(chalk.hex('#00D4FF')(' 🏪 Skills Marketplace') + chalk.hex('#6B6B7B')(' · skills.xibeai.in'));
1181
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────────'));
1182
- try {
1183
- const results = await skillManager.searchMarketplace(query, 10);
1184
- if (results.length === 0) {
1185
- console.log(' ' + chalk.hex('#6B6B7B')('No skills found' + (query ? ` for "${query}"` : '')));
1186
- console.log(' ' + chalk.dim('Try: /marketplace debug or /marketplace security'));
1187
- console.log('');
1188
- continue;
1189
- }
1190
- results.forEach((skill, i) => {
1191
- const score = skill.qualityScore ? chalk.hex('#00E676')(` ★${(skill.qualityScore * 10).toFixed(1)}`) : '';
1192
- const dl = chalk.hex('#6B6B7B')(`↓${skill.downloads}`);
1193
- console.log(' ' + chalk.white(`${i + 1}.`) + ' ' + chalk.hex('#00D4FF').bold(skill.name) + score + ' ' + dl);
1194
- console.log(' ' + chalk.hex('#6B6B7B')(skill.description || 'No description'));
1195
- if (skill.categories?.length) {
1196
- console.log(' ' + chalk.dim(skill.categories.join(', ')));
1197
- }
1198
- });
1199
- console.log('');
1200
- // Prompt to install
1201
- closeRL();
1202
- const { installChoice } = await inquirer.prompt([
1203
- {
1204
- type: 'list',
1205
- name: 'installChoice',
1206
- message: 'Install a skill?',
1207
- choices: [
1208
- ...results.map((s, i) => ({
1209
- name: `${i + 1}. ${s.name}`,
1210
- value: s.id,
1211
- })),
1212
- { name: 'Cancel', value: '__cancel__' },
1213
- ],
1214
- },
1215
- ]);
1216
- createRL();
1217
- if (installChoice === '__cancel__') {
1218
- continue;
1219
- }
1220
- const selectedSkill = results.find(s => s.id === installChoice);
1221
- if (!selectedSkill)
1222
- continue;
1223
- const installResult = await skillManager.installFromMarketplace(installChoice, selectedSkill.name, (msg) => console.log(' ' + chalk.hex('#6B6B7B')(` ${msg}`)));
1224
- if (installResult.success) {
1225
- console.log('');
1226
- ui.success(`Installed "${selectedSkill.name}"`);
1227
- console.log(' ' + chalk.hex('#6B6B7B')(`Saved to: ${installResult.filePath}`));
1228
- const activateName = selectedSkill.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
1229
- console.log(' ' + chalk.dim(`Activate with: /skill ${activateName}`));
1230
- console.log('');
1231
- }
1232
- else {
1233
- ui.error(`Failed to install: ${installResult.error}`);
1234
- }
1235
- }
1236
- catch (error) {
1237
- ui.error(`Marketplace error: ${error.message}`);
1238
- }
1239
- continue;
1240
- }
1241
- if (lowerMessage === '/details') {
1242
- const next = !ui.getShowDetails();
1243
- ui.setShowDetails(next);
1244
- config.set('showDetails', next);
1245
- ui.success(`Details ${next ? 'enabled' : 'disabled'}`);
1246
- continue;
1247
- }
1248
- if (lowerMessage === '/thinking') {
1249
- const next = !ui.getShowThinking();
1250
- ui.setShowThinking(next);
1251
- config.set('showThinking', next);
1252
- ui.success(`Thinking display ${next ? 'enabled' : 'disabled'}`);
1253
- continue;
1254
- }
1255
- if (lowerMessage === '/mcp') {
1256
- console.log('');
1257
- console.log(chalk.bold(' MCP Servers'));
1258
- console.log(' ' + chalk.hex('#6B6B7B')('────────────────────────────'));
1259
- try {
1260
- // Load configured servers and establish connections on-demand
1261
- const mcpServers = await config.getMCPServers();
1262
- const serverNames = Object.keys(mcpServers);
1263
- if (serverNames.length === 0) {
1264
- console.log(' ' + chalk.hex('#6B6B7B')('No MCP servers configured'));
1265
- console.log(' ' + chalk.dim('Configure servers with: xibecode mcp add'));
1266
- }
1267
- else {
1268
- console.log(' ' + chalk.hex('#6B6B7B')(`Connecting to ${serverNames.length} MCP server(s)...`));
1269
- for (const serverName of serverNames) {
1270
- const serverConfig = mcpServers[serverName];
1271
- // Skip servers that are already connected in this session
1272
- if (!mcpClientManager.getConnectedServers().includes(serverName)) {
1273
- try {
1274
- await mcpClientManager.connect(serverName, serverConfig);
1275
- const tools = mcpClientManager
1276
- .getAvailableTools()
1277
- .filter(t => t.serverName === serverName);
1278
- console.log(' ' + chalk.green(`✓ ${serverName} (${tools.length} tool(s))`));
1279
- }
1280
- catch (error) {
1281
- console.log(' ' + chalk.yellow(`✗ Failed to connect to ${serverName}: ${error.message}`));
1282
- }
1283
- }
1284
- }
1285
- }
1286
- const connectedServers = mcpClientManager.getConnectedServers();
1287
- if (connectedServers.length === 0) {
1288
- console.log(' ' + chalk.hex('#6B6B7B')('No MCP servers connected'));
1289
- console.log(' ' + chalk.dim('Configure servers with: xibecode config --add-mcp-server'));
1290
- }
1291
- else {
1292
- for (const serverName of connectedServers) {
1293
- const serverTools = mcpClientManager.getAvailableTools().filter(t => t.serverName === serverName);
1294
- const serverResources = mcpClientManager.getAvailableResources().filter(r => r.serverName === serverName);
1295
- const serverPrompts = mcpClientManager.getAvailablePrompts().filter(p => p.serverName === serverName);
1296
- console.log('');
1297
- console.log(' ' + chalk.hex('#00D4FF')(serverName));
1298
- console.log(' ' + chalk.hex('#6B6B7B')(`Tools: ${serverTools.length} | Resources: ${serverResources.length} | Prompts: ${serverPrompts.length}`));
1299
- if (serverTools.length > 0) {
1300
- console.log(' ' + chalk.dim('Tools:'));
1301
- serverTools.forEach(tool => {
1302
- console.log(' ' + chalk.hex('#00D4FF')(`${tool.name}`) + chalk.hex('#6B6B7B')(` - ${tool.description}`));
1303
- });
1304
- }
1305
- }
1306
- }
1307
- }
1308
- catch (error) {
1309
- console.log(' ' + chalk.yellow(`Failed to load MCP configuration: ${error.message}`));
1310
- }
1311
- console.log('');
1312
- continue;
1313
- }
1314
- if (trimmed.startsWith('@')) {
1315
- await handleAtPathFuzzy(trimmed);
1316
- continue;
1317
- }
1318
- if (trimmed.startsWith('!')) {
1319
- await handleShellBang(trimmed);
1320
- console.log('');
1321
- continue;
1322
- }
1323
- if (lowerMessage === 'exit' || lowerMessage === 'quit') {
1324
- const stats = agent.getStats();
1325
- console.log('');
1326
- if (stats.toolCalls > 0 || stats.iterations > 0) {
1327
- console.log(chalk.hex('#6B6B7B')(` session: ${stats.iterations} turns · ${stats.toolCalls} tool calls · ${stats.filesChanged} files changed`));
1328
- console.log('');
1329
- }
1330
- console.log(chalk.hex('#3A3A4A')(' ╭──────────────────────────────────╮'));
1331
- console.log(chalk.hex('#3A3A4A')(' │') + chalk.hex('#00D4FF')(' 👋 See you next time! ') + chalk.hex('#3A3A4A')('│'));
1332
- console.log(chalk.hex('#3A3A4A')(' ╰──────────────────────────────────╯'));
1333
- console.log('');
1334
- await sessionManager.saveMessagesAndStats({
1335
- id: currentSession.id,
1336
- messages: agent.getMessages(),
1337
- stats,
1338
- });
1339
- // Stop WebUI server
1340
- if (webUIServer) {
1341
- await webUIServer.stop();
1342
- }
1343
- break;
1344
- }
1345
- if (lowerMessage === 'clear') {
1346
- ui.clear();
1347
- if (!config.isHeaderMinimal()) {
1348
- ui.header('1.0.0');
1349
- }
1350
- ui.chatBanner(process.cwd(), model, baseUrl);
1351
- continue;
1352
- }
1353
- if (lowerMessage === '/ui condensed') {
1354
- condensedUI = !condensedUI;
1355
- ui.setCondensedUI(condensedUI);
1356
- ui.success(`Condensed UI ${condensedUI ? 'enabled' : 'disabled'}`);
1357
- continue;
1358
- }
1359
- if (lowerMessage === 'tools on') {
1360
- enableTools = true;
1361
- ui.success('Tools enabled');
1362
- continue;
1363
- }
1364
- if (lowerMessage === 'tools off') {
1365
- enableTools = false;
1366
- ui.success('Tools disabled');
1367
- continue;
1368
- }
1369
- // Reset per-message display flag
1370
- hasResponse = false;
1371
- // Check for explicit agent call (e.g. "@Agni ...")
1372
- // We do this BEFORE the main agent run
1373
- const agentMatch = trimmed.match(/^@(\w+)\s+(.+)/);
1374
- if (agentMatch) {
1375
- const name = agentMatch[1];
1376
- const task = agentMatch[2];
1377
- // Find mode by persona name
1378
- const targetModeEntry = Object.entries(MODE_CONFIG).find(([_, cfg]) => cfg.personaName.toLowerCase() === name.toLowerCase());
1379
- if (targetModeEntry) {
1380
- const [modeId, modeConfig] = targetModeEntry;
1381
- if (modeId !== currentMode) {
1382
- ui.info(`Switching to ${modeConfig.personaName} (${modeConfig.name})...`);
1383
- if (typeof agent.setModeFromUser === 'function') {
1384
- agent.setModeFromUser(modeId, `User called @${name}`);
1385
- }
1386
- currentMode = modeId;
1387
- if (config.isStatusBarEnabled()) {
1388
- ui.renderStatusBar({
1389
- model,
1390
- sessionTitle: currentSession.title,
1391
- cwd: process.cwd(),
1392
- toolsEnabled: enableTools,
1393
- themeName: ui.getThemeName(),
1394
- mode: currentMode,
1395
- });
1396
- }
1397
- }
1398
- // Update message to remove the trigger, or keep it depending on preference.
1399
- // Let's keep the full message so the agent sees who was addressed.
1400
- }
1401
- }
1402
- try {
1403
- const tools = enableTools ? toolExecutor.getTools() : [];
1404
- // Save undo point before AI turn
1405
- undoStack.push({ messages: [...agent.getMessages()], label: message.slice(0, 40) });
1406
- if (undoStack.length > MAX_UNDO)
1407
- undoStack.shift();
1408
- redoStack.length = 0; // clear redo on new action
1409
- // Broadcast user message to WebUI (only if from TUI, WebUI already has it)
1410
- if (messageSource === 'tui') {
1411
- SessionBridge.onTUIUserMessage(message);
1412
- }
1413
- isAgentRunning = true;
1414
- // Reset per-assistant-run diff viewer state.
1415
- lastDiffByPath.clear();
1416
- diffPreviewCount = 0;
1417
- // agent.run() resets its iteration/tool counters but KEEPS
1418
- // the conversation history (this.messages), so the AI has
1419
- // full context of everything discussed in this session.
1420
- await agent.run(message, tools, toolExecutor);
1421
- isAgentRunning = false;
1422
- const unhandled = agent.getInjectedMessages();
1423
- if (unhandled.length > 0) {
1424
- agent.clearInjectedMessages();
1425
- for (const msg of unhandled) {
1426
- inputQueue.push({ message: msg, source: 'tui' });
1427
- }
1428
- }
1429
- const stats = agent.getStats();
1430
- // Update SessionBridge with latest messages
1431
- SessionBridge.updateState({ messages: agent.getMessages() });
1432
- // Build tokens label for status bar
1433
- const tokensLabel = stats.totalTokens > 0
1434
- ? `${(stats.totalTokens / 1000).toFixed(1)}k${stats.costLabel ? ` · ${stats.costLabel}` : ''}`
1435
- : undefined;
1436
- const activeSkill = agent.getActiveSkill();
1437
- if (config.isStatusBarEnabled()) {
1438
- ui.renderStatusBar({
1439
- model,
1440
- sessionTitle: currentSession.title,
1441
- tokensLabel,
1442
- cwd: process.cwd(),
1443
- toolsEnabled: enableTools,
1444
- themeName: ui.getThemeName(),
1445
- mode: currentMode,
1446
- activeSkill,
1447
- });
1448
- }
1449
- await sessionManager.saveMessagesAndStats({
1450
- id: currentSession.id,
1451
- messages: agent.getMessages(),
1452
- stats,
1453
- });
1454
- }
1455
- catch (error) {
1456
- ui.error('Failed to process message', error);
1457
- }
1458
- console.log('');
1459
- }
1460
- // Cleanup: disconnect from any MCP servers connected during this session
1461
- await mcpClientManager.disconnectAll();
1462
- // Stop WebUI server if running
1463
- if (webUIServer) {
1464
- await webUIServer.stop();
8
+ catch (error) {
9
+ const message = error instanceof Error ? error.message : 'Unknown error starting chat session';
10
+ const root = createRoot({ exitOnCtrlC: true });
11
+ await exitWithMessage(root, message, { color: 'error', exitCode: 1 });
1465
12
  }
1466
13
  }
1467
14
  //# sourceMappingURL=chat.js.map