@defai.digital/ax-cli 3.5.2 → 3.6.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 (125) hide show
  1. package/.ax-cli/memory.json +8 -8
  2. package/README.md +27 -1
  3. package/dist/agent/chat-history-manager.d.ts +56 -0
  4. package/dist/agent/chat-history-manager.js +150 -0
  5. package/dist/agent/chat-history-manager.js.map +1 -0
  6. package/dist/agent/llm-agent.js +1 -1
  7. package/dist/agent/llm-agent.js.map +1 -1
  8. package/dist/agent/tool-manager.d.ts +39 -0
  9. package/dist/agent/tool-manager.js +76 -0
  10. package/dist/agent/tool-manager.js.map +1 -0
  11. package/dist/analyzers/ast/index.d.ts +9 -0
  12. package/dist/analyzers/ast/index.js +10 -0
  13. package/dist/analyzers/ast/index.js.map +1 -0
  14. package/dist/analyzers/ast/node-helpers.d.ts +81 -0
  15. package/dist/analyzers/ast/node-helpers.js +128 -0
  16. package/dist/analyzers/ast/node-helpers.js.map +1 -0
  17. package/dist/analyzers/ast/traverser.d.ts +67 -0
  18. package/dist/analyzers/ast/traverser.js +156 -0
  19. package/dist/analyzers/ast/traverser.js.map +1 -0
  20. package/dist/analyzers/best-practices/index.d.ts +10 -0
  21. package/dist/analyzers/best-practices/index.js +11 -0
  22. package/dist/analyzers/best-practices/index.js.map +1 -0
  23. package/dist/commands/setup.js +13 -5
  24. package/dist/commands/setup.js.map +1 -1
  25. package/dist/index.js +7 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/llm/client.d.ts +1 -0
  28. package/dist/llm/client.js +44 -0
  29. package/dist/llm/client.js.map +1 -1
  30. package/dist/mcp/ssrf-protection.d.ts +86 -0
  31. package/dist/mcp/ssrf-protection.js +313 -0
  32. package/dist/mcp/ssrf-protection.js.map +1 -0
  33. package/dist/mcp/validation.d.ts +4 -0
  34. package/dist/mcp/validation.js +122 -11
  35. package/dist/mcp/validation.js.map +1 -1
  36. package/dist/schemas/settings-schemas.d.ts +30 -0
  37. package/dist/schemas/settings-schemas.js +30 -0
  38. package/dist/schemas/settings-schemas.js.map +1 -1
  39. package/dist/tools/bash.d.ts +3 -2
  40. package/dist/tools/bash.js +31 -2
  41. package/dist/tools/bash.js.map +1 -1
  42. package/dist/tools/search.d.ts +1 -1
  43. package/dist/tools/search.js +121 -128
  44. package/dist/tools/search.js.map +1 -1
  45. package/dist/tools/text-editor.js +52 -15
  46. package/dist/tools/text-editor.js.map +1 -1
  47. package/dist/ui/components/status-bar.js +2 -2
  48. package/dist/ui/components/status-bar.js.map +1 -1
  49. package/dist/ui/components/toast-notification.js +0 -1
  50. package/dist/ui/components/toast-notification.js.map +1 -1
  51. package/dist/utils/audit-logger.d.ts +247 -0
  52. package/dist/utils/audit-logger.js +374 -0
  53. package/dist/utils/audit-logger.js.map +1 -0
  54. package/dist/utils/command-security.d.ts +85 -0
  55. package/dist/utils/command-security.js +200 -0
  56. package/dist/utils/command-security.js.map +1 -0
  57. package/dist/utils/encryption.d.ts +78 -0
  58. package/dist/utils/encryption.js +216 -0
  59. package/dist/utils/encryption.js.map +1 -0
  60. package/dist/utils/error-sanitizer.d.ts +119 -0
  61. package/dist/utils/error-sanitizer.js +253 -0
  62. package/dist/utils/error-sanitizer.js.map +1 -0
  63. package/dist/utils/input-sanitizer.d.ts +210 -0
  64. package/dist/utils/input-sanitizer.js +362 -0
  65. package/dist/utils/input-sanitizer.js.map +1 -0
  66. package/dist/utils/json-utils.d.ts +13 -0
  67. package/dist/utils/json-utils.js +55 -1
  68. package/dist/utils/json-utils.js.map +1 -1
  69. package/dist/utils/parallel-analyzer.js +29 -12
  70. package/dist/utils/parallel-analyzer.js.map +1 -1
  71. package/dist/utils/path-security.d.ts +90 -0
  72. package/dist/utils/path-security.js +328 -0
  73. package/dist/utils/path-security.js.map +1 -0
  74. package/dist/utils/process-pool.d.ts +105 -0
  75. package/dist/utils/process-pool.js +326 -0
  76. package/dist/utils/process-pool.js.map +1 -0
  77. package/dist/utils/rate-limiter.d.ts +207 -0
  78. package/dist/utils/rate-limiter.js +303 -0
  79. package/dist/utils/rate-limiter.js.map +1 -0
  80. package/dist/utils/settings-manager.js +83 -4
  81. package/dist/utils/settings-manager.js.map +1 -1
  82. package/eslint.config.js +3 -0
  83. package/package.json +1 -1
  84. package/.ax-cli/checkpoints/2025-11-20/checkpoint-11e9e0ba-c39d-4fd2-aa77-bc818811c921.json +0 -69
  85. package/.ax-cli/checkpoints/2025-11-20/checkpoint-2b260b98-b418-4c7c-9694-e2b94967e662.json +0 -24
  86. package/.ax-cli/checkpoints/2025-11-20/checkpoint-7e03601e-e8ab-4cd7-9841-a74b66adf78f.json +0 -69
  87. package/.ax-cli/checkpoints/2025-11-20/checkpoint-7f9c6562-771f-4fd0-adcf-9e7e9ac34ae8.json +0 -44
  88. package/.ax-cli/checkpoints/2025-11-20/checkpoint-e1ebe666-4c3a-4367-ba5c-27fe512a9c70.json +0 -24
  89. package/.ax-cli/checkpoints/2025-11-21/checkpoint-15743e7d-430c-4d76-b6fc-955d7a5c250c.json +0 -44
  90. package/.ax-cli/checkpoints/2025-11-21/checkpoint-25cf7679-0b3f-4988-83d7-704548fbba91.json +0 -69
  91. package/.ax-cli/checkpoints/2025-11-21/checkpoint-54aedbac-6db0-464e-8ebb-dbb3979e6dca.json +0 -24
  92. package/.ax-cli/checkpoints/2025-11-21/checkpoint-7658aed8-fe5d-4222-903f-1a7c63717ea7.json +0 -24
  93. package/.ax-cli/checkpoints/2025-11-21/checkpoint-c9c13497-40dc-4294-a327-6a5fc854eaa1.json +0 -69
  94. package/ax.config.json +0 -333
  95. package/dist/hooks/use-chat-reducer.d.ts +0 -61
  96. package/dist/hooks/use-chat-reducer.js +0 -118
  97. package/dist/hooks/use-chat-reducer.js.map +0 -1
  98. package/dist/hooks/use-enhanced-input.d.ts +0 -40
  99. package/dist/hooks/use-enhanced-input.js +0 -249
  100. package/dist/hooks/use-enhanced-input.js.map +0 -1
  101. package/dist/hooks/use-input-handler.d.ts +0 -46
  102. package/dist/hooks/use-input-handler.js +0 -1430
  103. package/dist/hooks/use-input-handler.js.map +0 -1
  104. package/dist/hooks/use-input-history.d.ts +0 -9
  105. package/dist/hooks/use-input-history.js +0 -112
  106. package/dist/hooks/use-input-history.js.map +0 -1
  107. package/dist/utils/paste-collapse.d.ts +0 -46
  108. package/dist/utils/paste-collapse.js +0 -77
  109. package/dist/utils/paste-collapse.js.map +0 -1
  110. package/packages/schemas/dist/index.d.ts +0 -14
  111. package/packages/schemas/dist/index.d.ts.map +0 -1
  112. package/packages/schemas/dist/index.js +0 -19
  113. package/packages/schemas/dist/index.js.map +0 -1
  114. package/packages/schemas/dist/public/core/brand-types.d.ts +0 -308
  115. package/packages/schemas/dist/public/core/brand-types.d.ts.map +0 -1
  116. package/packages/schemas/dist/public/core/brand-types.js +0 -243
  117. package/packages/schemas/dist/public/core/brand-types.js.map +0 -1
  118. package/packages/schemas/dist/public/core/enums.d.ts +0 -227
  119. package/packages/schemas/dist/public/core/enums.d.ts.map +0 -1
  120. package/packages/schemas/dist/public/core/enums.js +0 -222
  121. package/packages/schemas/dist/public/core/enums.js.map +0 -1
  122. package/packages/schemas/dist/public/core/id-types.d.ts +0 -286
  123. package/packages/schemas/dist/public/core/id-types.d.ts.map +0 -1
  124. package/packages/schemas/dist/public/core/id-types.js +0 -136
  125. package/packages/schemas/dist/public/core/id-types.js.map +0 -1
@@ -1,1430 +0,0 @@
1
- import { useState, useMemo, useEffect, useCallback, useRef } from "react";
2
- import { useInput } from "ink";
3
- import { ConfirmationService } from "../utils/confirmation-service.js";
4
- import { useEnhancedInput } from "./use-enhanced-input.js";
5
- import { escapeShellArg } from "../tools/bash.js";
6
- import { filterCommandSuggestions } from "../ui/components/command-suggestions.js";
7
- import { getSettingsManager } from "../utils/settings-manager.js";
8
- import { ProjectAnalyzer } from "../utils/project-analyzer.js";
9
- import { InstructionGenerator } from "../utils/instruction-generator.js";
10
- import { getUsageTracker } from "../utils/usage-tracker.js";
11
- import { getHistoryManager } from "../utils/history-manager.js";
12
- import { handleRewindCommand, handleCheckpointsCommand, handleCheckpointCleanCommand } from "../commands/rewind.js";
13
- import { handlePlansCommand, handlePlanCommand, handlePhasesCommand, handlePauseCommand, handleResumeCommand, handleSkipPhaseCommand, handleAbandonCommand, handleResumableCommand, } from "../commands/plan.js";
14
- import { BashOutputTool } from "../tools/bash-output.js";
15
- import { getKeyboardShortcutGuideText } from "../ui/components/keyboard-hints.js";
16
- import { getContextStore, ContextGenerator, getStatsCollector, } from "../memory/index.js";
17
- import * as fs from "fs";
18
- import * as path from "path";
19
- export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProcessing, setIsStreaming, setTokenCount, setProcessingTime, processingStartTime, isProcessing, isStreaming, isConfirmationActive = false, onQuickActionsToggle, onVerboseModeChange, onBackgroundModeChange, onAutoEditModeChange, onTaskMovedToBackground, onOperationInterrupted, onChatCleared, onCopyLastResponse, onMemoryWarmed, onMemoryRefreshed, onCheckpointCreated: _onCheckpointCreated, onCheckpointRestored: _onCheckpointRestored, }) {
20
- const [showCommandSuggestions, setShowCommandSuggestions] = useState(false);
21
- const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
22
- const retryTimeoutRef = useRef(null);
23
- const [autoEditEnabled, setAutoEditEnabled] = useState(() => {
24
- const confirmationService = ConfirmationService.getInstance();
25
- const sessionFlags = confirmationService.getSessionFlags();
26
- // Default to true (auto-apply enabled by default)
27
- return sessionFlags.allOperations !== undefined ? sessionFlags.allOperations : true;
28
- });
29
- const [verboseMode, setVerboseMode] = useState(false);
30
- const [backgroundMode, setBackgroundMode] = useState(false);
31
- const handleSpecialKey = (key) => {
32
- // Don't handle input if confirmation dialog is active
33
- if (isConfirmationActive) {
34
- return true; // Prevent default handling
35
- }
36
- // Handle shift+tab to toggle auto-edit mode
37
- if (key.shift && key.tab) {
38
- const newAutoEditState = !autoEditEnabled;
39
- setAutoEditEnabled(newAutoEditState);
40
- const confirmationService = ConfirmationService.getInstance();
41
- if (newAutoEditState) {
42
- // Enable auto-edit: set all operations to be accepted
43
- confirmationService.setSessionFlag("allOperations", true);
44
- }
45
- else {
46
- // Disable auto-edit: reset session flags
47
- confirmationService.resetSession();
48
- }
49
- // Notify parent for toast/flash feedback
50
- onAutoEditModeChange?.(newAutoEditState);
51
- return true; // Handled
52
- }
53
- // Handle escape key for closing menus
54
- if (key.escape) {
55
- if (showCommandSuggestions) {
56
- setShowCommandSuggestions(false);
57
- setSelectedCommandIndex(0);
58
- return true;
59
- }
60
- if (isProcessing || isStreaming) {
61
- agent.abortCurrentOperation();
62
- setIsProcessing(false);
63
- setIsStreaming(false);
64
- setTokenCount(0);
65
- setProcessingTime(0);
66
- processingStartTime.current = 0;
67
- // Notify parent for toast feedback
68
- onOperationInterrupted?.();
69
- return true;
70
- }
71
- return false; // Let default escape handling work
72
- }
73
- // Handle command suggestions navigation
74
- if (showCommandSuggestions) {
75
- const filteredSuggestions = filterCommandSuggestions(commandSuggestions, input);
76
- if (filteredSuggestions.length === 0) {
77
- setShowCommandSuggestions(false);
78
- setSelectedCommandIndex(0);
79
- return false; // Continue processing
80
- }
81
- else {
82
- if (key.upArrow) {
83
- setSelectedCommandIndex((prev) => prev === 0 ? filteredSuggestions.length - 1 : prev - 1);
84
- return true;
85
- }
86
- if (key.downArrow) {
87
- setSelectedCommandIndex((prev) => (prev + 1) % filteredSuggestions.length);
88
- return true;
89
- }
90
- if (key.tab || key.return) {
91
- // Check if there are any suggestions available
92
- if (filteredSuggestions.length === 0) {
93
- return true; // No suggestions, do nothing
94
- }
95
- const safeIndex = Math.min(selectedCommandIndex, filteredSuggestions.length - 1);
96
- const selectedCommand = filteredSuggestions[safeIndex];
97
- const newInput = selectedCommand.command + " ";
98
- setInput(newInput);
99
- setCursorPosition(newInput.length);
100
- setShowCommandSuggestions(false);
101
- setSelectedCommandIndex(0);
102
- return true;
103
- }
104
- }
105
- }
106
- return false; // Let default handling proceed
107
- };
108
- const handleInputSubmit = async (userInput) => {
109
- if (userInput === "exit" || userInput === "quit") {
110
- process.exit(0);
111
- return;
112
- }
113
- if (userInput.trim()) {
114
- const directCommandResult = await handleDirectCommand(userInput);
115
- if (!directCommandResult) {
116
- await processUserMessage(userInput);
117
- }
118
- }
119
- };
120
- const handleInputChange = useCallback((newInput) => {
121
- // Update command suggestions based on input
122
- if (newInput.startsWith("/")) {
123
- setShowCommandSuggestions(true);
124
- setSelectedCommandIndex(0);
125
- }
126
- else {
127
- setShowCommandSuggestions(false);
128
- setSelectedCommandIndex(0);
129
- }
130
- }, []);
131
- const handleVerboseToggle = useCallback(() => {
132
- setVerboseMode((prev) => {
133
- const newState = !prev;
134
- // Notify parent for toast/flash feedback instead of polluting chat history
135
- onVerboseModeChange?.(newState);
136
- return newState;
137
- });
138
- }, [onVerboseModeChange]);
139
- const handleBackgroundModeToggle = useCallback(() => {
140
- // Check if a bash command is currently executing
141
- if (agent.isBashExecuting()) {
142
- const taskId = agent.moveBashToBackground();
143
- if (taskId) {
144
- // Notify parent for toast feedback
145
- onTaskMovedToBackground?.(taskId);
146
- return;
147
- }
148
- }
149
- // Otherwise toggle background mode preference
150
- setBackgroundMode((prev) => {
151
- const newState = !prev;
152
- // Notify parent for toast/flash feedback instead of polluting chat history
153
- onBackgroundModeChange?.(newState);
154
- return newState;
155
- });
156
- }, [agent, onBackgroundModeChange, onTaskMovedToBackground]);
157
- const { input, cursorPosition, setInput, setCursorPosition, clearInput, resetHistory, handleInput, } = useEnhancedInput({
158
- onSubmit: handleInputSubmit,
159
- onSpecialKey: handleSpecialKey,
160
- onVerboseToggle: handleVerboseToggle,
161
- onQuickActions: onQuickActionsToggle,
162
- onBackgroundModeToggle: handleBackgroundModeToggle,
163
- onCopyLastResponse,
164
- disabled: isConfirmationActive,
165
- });
166
- // Hook up the actual input handling
167
- useInput((inputChar, key) => {
168
- handleInput(inputChar, key);
169
- });
170
- // Update command suggestions when input changes
171
- useEffect(() => {
172
- handleInputChange(input);
173
- }, [input, handleInputChange]);
174
- // Cleanup retry timeout on unmount
175
- useEffect(() => {
176
- return () => {
177
- if (retryTimeoutRef.current) {
178
- clearTimeout(retryTimeoutRef.current);
179
- retryTimeoutRef.current = null; // Clear ref to prevent dangling reference
180
- }
181
- };
182
- }, []);
183
- const commandSuggestions = [
184
- { command: "/help", description: "Show help information" },
185
- { command: "/shortcuts", description: "Show keyboard shortcuts guide" },
186
- { command: "/continue", description: "Continue incomplete response" },
187
- { command: "/retry", description: "Re-send the last message" },
188
- { command: "/clear", description: "Clear chat history" },
189
- { command: "/init", description: "Initialize project with smart analysis" },
190
- { command: "/usage", description: "Show API usage statistics" },
191
- { command: "/doctor", description: "Run health check diagnostics" },
192
- { command: "/tasks", description: "List background tasks" },
193
- { command: "/task", description: "View output of a background task" },
194
- { command: "/kill", description: "Kill a background task" },
195
- { command: "/rewind", description: "Rewind to previous checkpoint" },
196
- { command: "/checkpoints", description: "List checkpoint statistics" },
197
- { command: "/checkpoint-clean", description: "Clean old checkpoints" },
198
- { command: "/commit-and-push", description: "AI commit & push to remote" },
199
- { command: "/plans", description: "List all task plans" },
200
- { command: "/plan", description: "Show current plan details" },
201
- { command: "/phases", description: "Show phases of current plan" },
202
- { command: "/pause", description: "Pause current plan execution" },
203
- { command: "/resume", description: "Resume paused plan" },
204
- { command: "/skip", description: "Skip current phase" },
205
- { command: "/abandon", description: "Abandon current plan" },
206
- { command: "/memory", description: "Show project memory status" },
207
- { command: "/memory warmup", description: "Generate project memory" },
208
- { command: "/memory refresh", description: "Update project memory" },
209
- { command: "/exit", description: "Exit the application" },
210
- ];
211
- // Load models from configuration with fallback to defaults
212
- const availableModels = useMemo(() => {
213
- const settingsManager = getSettingsManager();
214
- const models = settingsManager.getAvailableModels();
215
- return models.map(m => ({ model: m }));
216
- }, []);
217
- const handleDirectCommand = async (input) => {
218
- const trimmedInput = input.trim();
219
- if (trimmedInput === "/continue") {
220
- // Send a shorter, more focused continuation prompt to avoid timeout
221
- // Using a brief prompt reduces token overhead for large contexts
222
- const continuePrompt = "Continue from where you left off.";
223
- // Add user continue command to history (showing the actual command for clarity)
224
- const userEntry = {
225
- type: "user",
226
- content: "/continue",
227
- timestamp: new Date(),
228
- };
229
- setChatHistory((prev) => [...prev, userEntry]);
230
- // Process through agent
231
- setIsProcessing(true);
232
- setIsStreaming(true);
233
- processingStartTime.current = Date.now();
234
- // Fire async operation with proper error handling
235
- (async () => {
236
- try {
237
- let streamingEntry = null;
238
- for await (const chunk of agent.processUserMessageStream(continuePrompt)) {
239
- switch (chunk.type) {
240
- case "reasoning":
241
- if (chunk.reasoningContent) {
242
- if (!streamingEntry) {
243
- const newStreamingEntry = {
244
- type: "assistant",
245
- content: "",
246
- timestamp: new Date(),
247
- isStreaming: true,
248
- reasoningContent: chunk.reasoningContent,
249
- isReasoningStreaming: true,
250
- };
251
- setChatHistory((prev) => [...prev, newStreamingEntry]);
252
- streamingEntry = newStreamingEntry;
253
- }
254
- else {
255
- setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
256
- ? {
257
- ...entry,
258
- reasoningContent: (entry.reasoningContent || "") +
259
- chunk.reasoningContent,
260
- isReasoningStreaming: true,
261
- }
262
- : entry));
263
- }
264
- }
265
- break;
266
- case "content":
267
- if (chunk.content) {
268
- if (!streamingEntry) {
269
- const newStreamingEntry = {
270
- type: "assistant",
271
- content: chunk.content,
272
- timestamp: new Date(),
273
- isStreaming: true,
274
- };
275
- setChatHistory((prev) => [...prev, newStreamingEntry]);
276
- streamingEntry = newStreamingEntry;
277
- }
278
- else {
279
- setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
280
- ? {
281
- ...entry,
282
- content: entry.content + chunk.content,
283
- isReasoningStreaming: false,
284
- }
285
- : entry));
286
- }
287
- }
288
- break;
289
- case "token_count":
290
- if (chunk.tokenCount !== undefined) {
291
- setTokenCount(chunk.tokenCount);
292
- }
293
- break;
294
- case "tool_calls":
295
- if (chunk.toolCalls) {
296
- setChatHistory((prev) => prev.map((entry) => entry.isStreaming
297
- ? {
298
- ...entry,
299
- isStreaming: false,
300
- toolCalls: chunk.toolCalls,
301
- }
302
- : entry));
303
- streamingEntry = null;
304
- chunk.toolCalls.forEach((toolCall) => {
305
- const toolCallEntry = {
306
- type: "tool_call",
307
- content: "Executing...",
308
- timestamp: new Date(),
309
- toolCall: toolCall,
310
- };
311
- setChatHistory((prev) => [...prev, toolCallEntry]);
312
- });
313
- }
314
- break;
315
- case "tool_result":
316
- if (chunk.toolCall && chunk.toolResult) {
317
- setChatHistory((prev) => prev.map((entry) => {
318
- if (entry.isStreaming) {
319
- return { ...entry, isStreaming: false };
320
- }
321
- if (entry.type === "tool_call" &&
322
- entry.toolCall?.id === chunk.toolCall?.id) {
323
- return {
324
- ...entry,
325
- type: "tool_result",
326
- content: chunk.toolResult?.success
327
- ? chunk.toolResult?.output || "Success"
328
- : chunk.toolResult?.error || "Error occurred",
329
- toolResult: chunk.toolResult,
330
- };
331
- }
332
- return entry;
333
- }));
334
- streamingEntry = null;
335
- }
336
- break;
337
- case "done":
338
- if (streamingEntry) {
339
- // Calculate response duration
340
- const durationMs = processingStartTime.current > 0
341
- ? Date.now() - processingStartTime.current
342
- : undefined;
343
- setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false, durationMs } : entry));
344
- }
345
- setIsStreaming(false);
346
- break;
347
- }
348
- }
349
- }
350
- catch (error) {
351
- const errorObj = error instanceof Error ? error : new Error(String(error));
352
- let errorMessage = `Error: ${errorObj.message}`;
353
- // Provide helpful guidance for timeout errors during /continue
354
- if (errorObj.message?.includes('timeout')) {
355
- errorMessage += `\n\n💡 Tip: For very long conversations, try:\n`;
356
- errorMessage += ` • Use /clear to start fresh and ask a more focused question\n`;
357
- errorMessage += ` • Break down your request into smaller parts\n`;
358
- errorMessage += ` • Use --continue flag to start a new session with history`;
359
- }
360
- const errorEntry = {
361
- type: "assistant",
362
- content: errorMessage,
363
- timestamp: new Date(),
364
- };
365
- setChatHistory((prev) => [...prev, errorEntry]);
366
- setIsStreaming(false);
367
- }
368
- setIsProcessing(false);
369
- processingStartTime.current = 0;
370
- })().catch((error) => {
371
- // Safety net: handle any uncaught errors from the async IIFE
372
- const errorObj = error instanceof Error ? error : new Error(String(error));
373
- const errorEntry = {
374
- type: "assistant",
375
- content: `Unexpected error: ${errorObj.message}`,
376
- timestamp: new Date(),
377
- };
378
- setChatHistory((prev) => [...prev, errorEntry]);
379
- setIsStreaming(false);
380
- setIsProcessing(false);
381
- processingStartTime.current = 0;
382
- });
383
- clearInput();
384
- resetHistory();
385
- return true;
386
- }
387
- if (trimmedInput === "/retry") {
388
- // Find the last user message index and re-send it
389
- // Use findLastIndex instead of reverse().find() + lastIndexOf() to avoid object reference issues
390
- const lastUserIndex = chatHistory.findLastIndex(entry => entry.type === "user");
391
- if (lastUserIndex >= 0 && chatHistory[lastUserIndex]?.content) {
392
- // Store the message content and history state before clearing
393
- const messageToRetry = chatHistory[lastUserIndex].content;
394
- const historyBackup = [...chatHistory];
395
- // Remove the last user message and any assistant responses after it
396
- setChatHistory(prev => prev.slice(0, lastUserIndex));
397
- clearInput();
398
- // Trigger submit after a brief delay to allow state update
399
- // Track timeout for cleanup on unmount
400
- retryTimeoutRef.current = setTimeout(() => {
401
- retryTimeoutRef.current = null;
402
- try {
403
- handleInputSubmit(messageToRetry);
404
- }
405
- catch {
406
- // Restore history if retry fails
407
- setChatHistory(historyBackup);
408
- }
409
- }, 50);
410
- }
411
- else {
412
- clearInput();
413
- }
414
- return true;
415
- }
416
- if (trimmedInput === "/clear") {
417
- // Reset chat history
418
- setChatHistory([]);
419
- // Clear saved history from disk
420
- const historyManager = getHistoryManager();
421
- historyManager.clearHistory();
422
- // Reset processing states
423
- setIsProcessing(false);
424
- setIsStreaming(false);
425
- setTokenCount(0);
426
- setProcessingTime(0);
427
- processingStartTime.current = 0;
428
- // Reset confirmation service session flags
429
- const confirmationService = ConfirmationService.getInstance();
430
- confirmationService.resetSession();
431
- // Notify parent for toast feedback
432
- onChatCleared?.();
433
- clearInput();
434
- resetHistory();
435
- return true;
436
- }
437
- if (trimmedInput === "/init") {
438
- const userEntry = {
439
- type: "user",
440
- content: "/init",
441
- timestamp: new Date(),
442
- };
443
- setChatHistory((prev) => [...prev, userEntry]);
444
- setIsProcessing(true);
445
- try {
446
- const projectRoot = process.cwd();
447
- // Add analysis message
448
- const analyzingEntry = {
449
- type: "assistant",
450
- content: "🔍 Analyzing project...\n",
451
- timestamp: new Date(),
452
- };
453
- setChatHistory((prev) => [...prev, analyzingEntry]);
454
- // Check if already initialized
455
- const axCliDir = path.join(projectRoot, ".ax-cli");
456
- const customMdPath = path.join(axCliDir, "CUSTOM.md");
457
- const indexPath = path.join(axCliDir, "index.json");
458
- if (fs.existsSync(customMdPath)) {
459
- const alreadyInitEntry = {
460
- type: "assistant",
461
- content: `✅ Project already initialized!\n📝 Custom instructions: ${customMdPath}\n📊 Project index: ${indexPath}\n\n💡 Run 'ax-cli init --force' from terminal to regenerate`,
462
- timestamp: new Date(),
463
- };
464
- setChatHistory((prev) => [...prev, alreadyInitEntry]);
465
- setIsProcessing(false);
466
- clearInput();
467
- return true;
468
- }
469
- // Analyze project
470
- const analyzer = new ProjectAnalyzer(projectRoot);
471
- const result = await analyzer.analyze();
472
- if (!result.success || !result.projectInfo) {
473
- const errorEntry = {
474
- type: "assistant",
475
- content: `❌ Failed to analyze project: ${result.error || "Unknown error"}`,
476
- timestamp: new Date(),
477
- };
478
- setChatHistory((prev) => [...prev, errorEntry]);
479
- setIsProcessing(false);
480
- clearInput();
481
- return true;
482
- }
483
- const projectInfo = result.projectInfo;
484
- // Generate instructions
485
- const generator = new InstructionGenerator();
486
- const instructions = generator.generateInstructions(projectInfo);
487
- const index = generator.generateIndex(projectInfo);
488
- // Create .ax-cli directory
489
- if (!fs.existsSync(axCliDir)) {
490
- fs.mkdirSync(axCliDir, { recursive: true });
491
- }
492
- // Write custom instructions
493
- fs.writeFileSync(customMdPath, instructions, "utf-8");
494
- // Write project index
495
- fs.writeFileSync(indexPath, index, "utf-8");
496
- // Display success
497
- let successMessage = `🎉 Project initialized successfully!\n\n`;
498
- successMessage += `📋 Analysis Results:\n`;
499
- successMessage += ` Name: ${projectInfo.name}\n`;
500
- successMessage += ` Type: ${projectInfo.projectType}\n`;
501
- successMessage += ` Language: ${projectInfo.primaryLanguage}\n`;
502
- if (projectInfo.techStack.length > 0) {
503
- successMessage += ` Stack: ${projectInfo.techStack.join(", ")}\n`;
504
- }
505
- successMessage += `\n✅ Generated custom instructions: ${customMdPath}\n`;
506
- successMessage += `✅ Generated project index: ${indexPath}\n\n`;
507
- successMessage += `💡 Next steps:\n`;
508
- successMessage += ` 1. Review and customize the instructions if needed\n`;
509
- successMessage += ` 2. Run AX CLI - it will automatically use these instructions\n`;
510
- successMessage += ` 3. Use 'ax-cli init --force' to regenerate after project changes`;
511
- const successEntry = {
512
- type: "assistant",
513
- content: successMessage,
514
- timestamp: new Date(),
515
- };
516
- setChatHistory((prev) => [...prev, successEntry]);
517
- }
518
- catch (error) {
519
- const errorEntry = {
520
- type: "assistant",
521
- content: `❌ Error during initialization: ${error instanceof Error ? error.message : "Unknown error"}`,
522
- timestamp: new Date(),
523
- };
524
- setChatHistory((prev) => [...prev, errorEntry]);
525
- }
526
- setIsProcessing(false);
527
- clearInput();
528
- return true;
529
- }
530
- if (trimmedInput === "/help") {
531
- const helpEntry = {
532
- type: "assistant",
533
- content: `AX CLI Help:
534
-
535
- Built-in Commands:
536
- /continue - Continue incomplete response from where it left off
537
- /clear - Clear chat history
538
- /init - Initialize project with smart analysis
539
- /help - Show this help
540
- /shortcuts - Show keyboard shortcuts guide
541
- /usage - Show API usage statistics
542
- /doctor - Run health check diagnostics
543
- /exit - Exit application
544
- exit, quit - Exit application
545
-
546
- Background Task Commands:
547
- /tasks - List all background tasks
548
- /task <id> - View output of a background task
549
- /kill <id> - Kill a running background task
550
-
551
- Tip: Append ' &' to any bash command to run it in background
552
- Example: npm run dev &
553
-
554
- Checkpoint Commands:
555
- /rewind - Rewind to a previous checkpoint (interactive)
556
- /checkpoints - Show checkpoint statistics
557
- /checkpoint-clean - Clean old checkpoints (compress and prune)
558
-
559
- Plan Commands (Multi-Phase Task Planning):
560
- /plans - List all task plans
561
- /plan [id] - Show current or specific plan details
562
- /phases - Show phases of current plan
563
- /pause - Pause current plan execution
564
- /resume [id] - Resume current or specific plan
565
- /skip - Skip current phase
566
- /abandon - Abandon current plan
567
-
568
- Git Commands:
569
- /commit-and-push - AI-generated commit + push to remote
570
-
571
- Memory Commands (z.ai GLM-4.6 caching):
572
- /memory - Show project memory status
573
- /memory warmup - Generate project memory context
574
- /memory refresh - Update memory after changes
575
-
576
- Enhanced Input Features:
577
- ↑/↓ Arrow - Navigate command history
578
- Ctrl+C - Clear input (press twice to exit)
579
- Ctrl+X - Clear entire input line
580
- Ctrl+←/→ - Move by word
581
- Ctrl+A/E - Move to line start/end
582
- Ctrl+W - Delete word before cursor
583
- Ctrl+K - Delete to end of line
584
- Ctrl+U - Delete to start of line
585
- Ctrl+O - Toggle verbose mode (show full output, default: concise)
586
- Ctrl+B - Toggle background mode (run all commands in background)
587
- Shift+Tab - Toggle auto-edit mode (bypass confirmations)
588
- 1-4 keys - Quick select in confirmation dialogs
589
-
590
- Direct Commands (executed immediately):
591
- ls [path] - List directory contents
592
- pwd - Show current directory
593
- cd <path> - Change directory
594
- cat <file> - View file contents
595
- mkdir <dir> - Create directory
596
- touch <file>- Create empty file
597
-
598
- Model Configuration:
599
- Edit ~/.ax-cli/config.json to configure default model and settings
600
-
601
- For complex operations, just describe what you want in natural language.
602
- Examples:
603
- "edit package.json and add a new script"
604
- "create a new React component called Header"
605
- "show me all TypeScript files in this project"`,
606
- timestamp: new Date(),
607
- };
608
- setChatHistory((prev) => [...prev, helpEntry]);
609
- clearInput();
610
- return true;
611
- }
612
- if (trimmedInput === "/shortcuts") {
613
- const shortcutsEntry = {
614
- type: "assistant",
615
- content: getKeyboardShortcutGuideText(),
616
- timestamp: new Date(),
617
- };
618
- setChatHistory((prev) => [...prev, shortcutsEntry]);
619
- clearInput();
620
- return true;
621
- }
622
- if (trimmedInput === "/usage") {
623
- const tracker = getUsageTracker();
624
- const stats = tracker.getSessionStats();
625
- let usageContent = "📊 **API Usage & Limits (Z.AI)**\n\n";
626
- // Session statistics
627
- usageContent += "**📱 Current Session:**\n";
628
- if (stats.totalRequests === 0) {
629
- usageContent += " No API requests made yet. Ask me something to start tracking!\n";
630
- }
631
- else {
632
- usageContent += ` • Requests: ${stats.totalRequests.toLocaleString()}\n`;
633
- usageContent += ` • Prompt Tokens: ${stats.totalPromptTokens.toLocaleString()}\n`;
634
- usageContent += ` • Completion Tokens: ${stats.totalCompletionTokens.toLocaleString()}\n`;
635
- usageContent += ` • Total Tokens: ${stats.totalTokens.toLocaleString()}\n`;
636
- if (stats.totalReasoningTokens > 0) {
637
- usageContent += ` • Reasoning Tokens: ${stats.totalReasoningTokens.toLocaleString()}\n`;
638
- }
639
- if (stats.byModel.size > 0) {
640
- usageContent += `\n **Models Used:**\n`;
641
- for (const [model, modelStats] of stats.byModel.entries()) {
642
- usageContent += ` - ${model}: ${modelStats.totalTokens.toLocaleString()} tokens (${modelStats.requests} requests)\n`;
643
- }
644
- }
645
- }
646
- // Z.AI account information
647
- usageContent += `\n**🔑 Z.AI Account Usage & Limits:**\n`;
648
- usageContent += ` ⚠️ API does not provide programmatic access to usage data\n`;
649
- usageContent += `\n **Check your account:**\n`;
650
- usageContent += ` • Billing & Usage: https://z.ai/manage-apikey/billing\n`;
651
- usageContent += ` • Rate Limits: https://z.ai/manage-apikey/rate-limits\n`;
652
- usageContent += ` • API Keys: https://z.ai/manage-apikey/apikey-list\n`;
653
- usageContent += `\n**ℹ️ Notes:**\n`;
654
- usageContent += ` • Billing reflects previous day (n-1) consumption\n`;
655
- usageContent += ` • Current day usage may not be immediately visible\n`;
656
- usageContent += ` • Cached content: 1/5 of original price\n`;
657
- usageContent += `\n**💰 GLM-4.6 Pricing:**\n`;
658
- usageContent += ` • Input: $0.11 per 1M tokens\n`;
659
- usageContent += ` • Output: $0.28 per 1M tokens\n`;
660
- if (stats.totalRequests > 0) {
661
- // Calculate estimated cost for this session
662
- const inputCost = (stats.totalPromptTokens / 1000000) * 0.11;
663
- const outputCost = (stats.totalCompletionTokens / 1000000) * 0.28;
664
- const totalCost = inputCost + outputCost;
665
- usageContent += `\n**💵 Estimated Session Cost:**\n`;
666
- usageContent += ` • Input: $${inputCost.toFixed(6)} (${stats.totalPromptTokens.toLocaleString()} tokens)\n`;
667
- usageContent += ` • Output: $${outputCost.toFixed(6)} (${stats.totalCompletionTokens.toLocaleString()} tokens)\n`;
668
- usageContent += ` • **Total: ~$${totalCost.toFixed(6)}**\n`;
669
- }
670
- const usageEntry = {
671
- type: "assistant",
672
- content: usageContent,
673
- timestamp: new Date(),
674
- };
675
- setChatHistory((prev) => [...prev, usageEntry]);
676
- clearInput();
677
- return true;
678
- }
679
- if (trimmedInput === "/doctor") {
680
- // Run doctor diagnostics
681
- const doctorContent = "🏥 **Running AX CLI Diagnostics...**\n\n";
682
- const doctorEntry = {
683
- type: "assistant",
684
- content: doctorContent,
685
- timestamp: new Date(),
686
- };
687
- setChatHistory((prev) => [...prev, doctorEntry]);
688
- // Execute doctor command asynchronously (non-blocking)
689
- (async () => {
690
- try {
691
- const { exec } = await import("child_process");
692
- const { promisify } = await import("util");
693
- const execAsync = promisify(exec);
694
- const { stdout, stderr } = await execAsync("node dist/index.js doctor", {
695
- encoding: "utf-8",
696
- cwd: process.cwd(),
697
- timeout: 30000, // 30 second timeout
698
- maxBuffer: 10 * 1024 * 1024, // 10MB buffer
699
- });
700
- const output = stdout || stderr;
701
- const resultEntry = {
702
- type: "assistant",
703
- content: `\`\`\`\n${output}\n\`\`\``,
704
- timestamp: new Date(),
705
- };
706
- setChatHistory((prev) => [...prev, resultEntry]);
707
- }
708
- catch (error) {
709
- const errorEntry = {
710
- type: "assistant",
711
- content: `❌ **Doctor diagnostics failed:**\n\n\`\`\`\n${error.message}\n\`\`\``,
712
- timestamp: new Date(),
713
- };
714
- setChatHistory((prev) => [...prev, errorEntry]);
715
- }
716
- })();
717
- clearInput();
718
- return true;
719
- }
720
- if (trimmedInput === "/exit") {
721
- process.exit(0);
722
- return true;
723
- }
724
- // Memory commands
725
- if (trimmedInput === "/memory" || trimmedInput === "/memory status") {
726
- const store = getContextStore();
727
- const metadata = store.getMetadata();
728
- let memoryContent = "🧠 **Project Memory Status**\n\n";
729
- if (!metadata.exists) {
730
- memoryContent += "❌ No project memory found.\n\n";
731
- memoryContent += "Run `/memory warmup` to generate project memory for z.ai caching.\n";
732
- }
733
- else {
734
- memoryContent += `✅ Memory initialized\n\n`;
735
- memoryContent += `**Token Estimate:** ${metadata.tokenEstimate?.toLocaleString() || 'N/A'} tokens\n`;
736
- memoryContent += `**Last Updated:** ${metadata.updatedAt ? new Date(metadata.updatedAt).toLocaleString() : 'N/A'}\n`;
737
- memoryContent += `**Usage Count:** ${metadata.usageCount || 0}\n`;
738
- // Try to get section breakdown
739
- const loadResult = store.load();
740
- if (loadResult.success) {
741
- const sections = loadResult.data.context.sections;
742
- memoryContent += `\n**📊 Token Distribution:**\n`;
743
- const total = Object.values(sections).reduce((a, b) => a + b, 0);
744
- for (const [name, tokens] of Object.entries(sections)) {
745
- const pct = total > 0 ? Math.round((tokens / total) * 100) : 0;
746
- const bar = '█'.repeat(Math.round(pct / 5)) + '░'.repeat(20 - Math.round(pct / 5));
747
- memoryContent += ` ${bar} ${name.charAt(0).toUpperCase() + name.slice(1)} (${pct}%)\n`;
748
- }
749
- }
750
- // Show cache stats if available
751
- const statsCollector = getStatsCollector();
752
- const formattedStats = statsCollector.getFormattedStats();
753
- if (formattedStats && formattedStats.usageCount > 0) {
754
- memoryContent += `\n**💾 Cache Statistics:**\n`;
755
- memoryContent += ` • Usage Count: ${formattedStats.usageCount}\n`;
756
- memoryContent += ` • Tokens Saved: ${formattedStats.tokensSaved.toLocaleString()}\n`;
757
- memoryContent += ` • Cache Rate: ${formattedStats.cacheRate}%\n`;
758
- memoryContent += ` • Est. Savings: $${formattedStats.estimatedSavings.toFixed(4)}\n`;
759
- }
760
- }
761
- const memoryEntry = {
762
- type: "assistant",
763
- content: memoryContent,
764
- timestamp: new Date(),
765
- };
766
- setChatHistory((prev) => [...prev, memoryEntry]);
767
- clearInput();
768
- return true;
769
- }
770
- if (trimmedInput === "/memory warmup") {
771
- setIsProcessing(true);
772
- const warmupEntry = {
773
- type: "assistant",
774
- content: "🔄 Generating project memory...",
775
- timestamp: new Date(),
776
- };
777
- // Track the entry index BEFORE any async operations to prevent race conditions
778
- let entryIndex = -1;
779
- setChatHistory((prev) => {
780
- entryIndex = prev.length; // Store index before pushing
781
- return [...prev, warmupEntry];
782
- });
783
- try {
784
- const generator = new ContextGenerator();
785
- const result = await generator.generate();
786
- if (result.success) {
787
- const store = getContextStore();
788
- const saveResult = store.save(result.memory);
789
- if (saveResult.success) {
790
- const sections = result.memory.context.sections;
791
- const tokenEstimate = result.memory.context.token_estimate;
792
- let resultContent = `✅ Project memory generated (${tokenEstimate.toLocaleString()} tokens)\n\n`;
793
- resultContent += `**📊 Context breakdown:**\n`;
794
- for (const [name, tokens] of Object.entries(sections)) {
795
- if (tokens !== undefined) {
796
- const tokenCount = tokens;
797
- const pct = Math.round((tokenCount / tokenEstimate) * 100);
798
- resultContent += ` ${name.charAt(0).toUpperCase() + name.slice(1)}: ${tokenCount.toLocaleString()} tokens (${pct}%)\n`;
799
- }
800
- }
801
- resultContent += `\n💾 Saved to .ax-cli/memory.json`;
802
- // Update the specific entry by index to avoid race conditions
803
- setChatHistory((prev) => {
804
- const updated = [...prev];
805
- if (entryIndex >= 0 && entryIndex < updated.length) {
806
- updated[entryIndex] = {
807
- type: "assistant",
808
- content: resultContent,
809
- timestamp: new Date(),
810
- };
811
- }
812
- return updated;
813
- });
814
- // Trigger toast notification
815
- onMemoryWarmed?.(tokenEstimate);
816
- }
817
- else {
818
- throw new Error(saveResult.error);
819
- }
820
- }
821
- else {
822
- throw new Error(result.error);
823
- }
824
- }
825
- catch (error) {
826
- // Update the specific entry by index to avoid race conditions
827
- setChatHistory((prev) => {
828
- const updated = [...prev];
829
- if (entryIndex >= 0 && entryIndex < updated.length) {
830
- updated[entryIndex] = {
831
- type: "assistant",
832
- content: `❌ Failed to generate memory: ${error.message}`,
833
- timestamp: new Date(),
834
- };
835
- }
836
- return updated;
837
- });
838
- }
839
- setIsProcessing(false);
840
- clearInput();
841
- return true;
842
- }
843
- if (trimmedInput === "/memory refresh") {
844
- setIsProcessing(true);
845
- const refreshEntry = {
846
- type: "assistant",
847
- content: "🔄 Refreshing project memory...",
848
- timestamp: new Date(),
849
- };
850
- // Track the entry index BEFORE any async operations to prevent race conditions
851
- let entryIndex = -1;
852
- setChatHistory((prev) => {
853
- entryIndex = prev.length; // Store index before pushing
854
- return [...prev, refreshEntry];
855
- });
856
- try {
857
- const store = getContextStore();
858
- const existing = store.load();
859
- const generator = new ContextGenerator();
860
- const result = await generator.generate();
861
- if (result.success) {
862
- const hasChanges = !existing.success ||
863
- existing.data.content_hash !== result.memory.content_hash;
864
- if (hasChanges) {
865
- const saveResult = store.save(result.memory);
866
- if (saveResult.success) {
867
- // Update the specific entry by index to avoid race conditions
868
- setChatHistory((prev) => {
869
- const updated = [...prev];
870
- if (entryIndex >= 0 && entryIndex < updated.length) {
871
- updated[entryIndex] = {
872
- type: "assistant",
873
- content: `✅ Memory updated (${result.memory.context.token_estimate.toLocaleString()} tokens)`,
874
- timestamp: new Date(),
875
- };
876
- }
877
- return updated;
878
- });
879
- // Trigger toast notification
880
- onMemoryRefreshed?.();
881
- }
882
- else {
883
- throw new Error(saveResult.error);
884
- }
885
- }
886
- else {
887
- // Update the specific entry by index to avoid race conditions
888
- setChatHistory((prev) => {
889
- const updated = [...prev];
890
- if (entryIndex >= 0 && entryIndex < updated.length) {
891
- updated[entryIndex] = {
892
- type: "assistant",
893
- content: `✅ No changes detected - memory is up to date`,
894
- timestamp: new Date(),
895
- };
896
- }
897
- return updated;
898
- });
899
- }
900
- }
901
- else {
902
- throw new Error(result.error);
903
- }
904
- }
905
- catch (error) {
906
- // Update the specific entry by index to avoid race conditions
907
- setChatHistory((prev) => {
908
- const updated = [...prev];
909
- if (entryIndex >= 0 && entryIndex < updated.length) {
910
- updated[entryIndex] = {
911
- type: "assistant",
912
- content: `❌ Failed to refresh memory: ${error.message}`,
913
- timestamp: new Date(),
914
- };
915
- }
916
- return updated;
917
- });
918
- }
919
- setIsProcessing(false);
920
- clearInput();
921
- return true;
922
- }
923
- // Background task commands
924
- if (trimmedInput === "/tasks") {
925
- const bashOutputTool = new BashOutputTool();
926
- const result = bashOutputTool.listTasks();
927
- const tasksEntry = {
928
- type: "assistant",
929
- content: result.output || "No background tasks",
930
- timestamp: new Date(),
931
- };
932
- setChatHistory((prev) => [...prev, tasksEntry]);
933
- clearInput();
934
- return true;
935
- }
936
- if (trimmedInput.startsWith("/task ")) {
937
- const taskId = trimmedInput.substring(6).trim();
938
- if (!taskId) {
939
- const errorEntry = {
940
- type: "assistant",
941
- content: "Usage: /task <task_id>",
942
- timestamp: new Date(),
943
- };
944
- setChatHistory((prev) => [...prev, errorEntry]);
945
- clearInput();
946
- return true;
947
- }
948
- const bashOutputTool = new BashOutputTool();
949
- const result = await bashOutputTool.execute(taskId);
950
- const taskEntry = {
951
- type: "assistant",
952
- content: result.success ? result.output || "No output" : result.error || "Task not found",
953
- timestamp: new Date(),
954
- };
955
- setChatHistory((prev) => [...prev, taskEntry]);
956
- clearInput();
957
- return true;
958
- }
959
- if (trimmedInput.startsWith("/kill ")) {
960
- const taskId = trimmedInput.substring(6).trim();
961
- if (!taskId) {
962
- const errorEntry = {
963
- type: "assistant",
964
- content: "Usage: /kill <task_id>",
965
- timestamp: new Date(),
966
- };
967
- setChatHistory((prev) => [...prev, errorEntry]);
968
- clearInput();
969
- return true;
970
- }
971
- const bashOutputTool = new BashOutputTool();
972
- const result = bashOutputTool.killTask(taskId);
973
- const killEntry = {
974
- type: "assistant",
975
- content: result.success ? result.output || "Task killed" : result.error || "Failed to kill task",
976
- timestamp: new Date(),
977
- };
978
- setChatHistory((prev) => [...prev, killEntry]);
979
- clearInput();
980
- return true;
981
- }
982
- if (trimmedInput === "/rewind") {
983
- await handleRewindCommand(agent);
984
- clearInput();
985
- return true;
986
- }
987
- if (trimmedInput === "/checkpoints") {
988
- await handleCheckpointsCommand();
989
- clearInput();
990
- return true;
991
- }
992
- if (trimmedInput === "/checkpoint-clean") {
993
- await handleCheckpointCleanCommand();
994
- clearInput();
995
- return true;
996
- }
997
- // Plan commands
998
- if (trimmedInput === "/plans") {
999
- const result = await handlePlansCommand();
1000
- const plansEntry = {
1001
- type: "assistant",
1002
- content: result.output,
1003
- timestamp: new Date(),
1004
- };
1005
- setChatHistory((prev) => [...prev, plansEntry]);
1006
- clearInput();
1007
- return true;
1008
- }
1009
- if (trimmedInput === "/plan" || trimmedInput.startsWith("/plan ")) {
1010
- const planId = trimmedInput === "/plan" ? undefined : trimmedInput.substring(6).trim();
1011
- const result = await handlePlanCommand(planId || undefined);
1012
- const planEntry = {
1013
- type: "assistant",
1014
- content: result.output,
1015
- timestamp: new Date(),
1016
- };
1017
- setChatHistory((prev) => [...prev, planEntry]);
1018
- clearInput();
1019
- return true;
1020
- }
1021
- if (trimmedInput === "/phases") {
1022
- const result = await handlePhasesCommand();
1023
- const phasesEntry = {
1024
- type: "assistant",
1025
- content: result.output,
1026
- timestamp: new Date(),
1027
- };
1028
- setChatHistory((prev) => [...prev, phasesEntry]);
1029
- clearInput();
1030
- return true;
1031
- }
1032
- if (trimmedInput === "/pause") {
1033
- const result = await handlePauseCommand();
1034
- const pauseEntry = {
1035
- type: "assistant",
1036
- content: result.output,
1037
- timestamp: new Date(),
1038
- };
1039
- setChatHistory((prev) => [...prev, pauseEntry]);
1040
- clearInput();
1041
- return true;
1042
- }
1043
- if (trimmedInput === "/resume" || trimmedInput.startsWith("/resume ")) {
1044
- const planId = trimmedInput === "/resume" ? undefined : trimmedInput.substring(8).trim();
1045
- const result = await handleResumeCommand(planId || undefined);
1046
- const resumeEntry = {
1047
- type: "assistant",
1048
- content: result.output,
1049
- timestamp: new Date(),
1050
- };
1051
- setChatHistory((prev) => [...prev, resumeEntry]);
1052
- clearInput();
1053
- return true;
1054
- }
1055
- if (trimmedInput === "/skip") {
1056
- const result = await handleSkipPhaseCommand();
1057
- const skipEntry = {
1058
- type: "assistant",
1059
- content: result.output,
1060
- timestamp: new Date(),
1061
- };
1062
- setChatHistory((prev) => [...prev, skipEntry]);
1063
- clearInput();
1064
- return true;
1065
- }
1066
- if (trimmedInput === "/abandon") {
1067
- const result = await handleAbandonCommand();
1068
- const abandonEntry = {
1069
- type: "assistant",
1070
- content: result.output,
1071
- timestamp: new Date(),
1072
- };
1073
- setChatHistory((prev) => [...prev, abandonEntry]);
1074
- clearInput();
1075
- return true;
1076
- }
1077
- if (trimmedInput === "/resumable") {
1078
- const result = await handleResumableCommand();
1079
- const resumableEntry = {
1080
- type: "assistant",
1081
- content: result.output,
1082
- timestamp: new Date(),
1083
- };
1084
- setChatHistory((prev) => [...prev, resumableEntry]);
1085
- clearInput();
1086
- return true;
1087
- }
1088
- if (trimmedInput === "/commit-and-push") {
1089
- const userEntry = {
1090
- type: "user",
1091
- content: "/commit-and-push",
1092
- timestamp: new Date(),
1093
- };
1094
- setChatHistory((prev) => [...prev, userEntry]);
1095
- setIsProcessing(true);
1096
- setIsStreaming(true);
1097
- try {
1098
- // First check if there are any changes at all
1099
- const initialStatusResult = await agent.executeBashCommand("git status --porcelain");
1100
- if (!initialStatusResult.success ||
1101
- !initialStatusResult.output?.trim()) {
1102
- const noChangesEntry = {
1103
- type: "assistant",
1104
- content: "No changes to commit. Working directory is clean.",
1105
- timestamp: new Date(),
1106
- };
1107
- setChatHistory((prev) => [...prev, noChangesEntry]);
1108
- setIsProcessing(false);
1109
- setIsStreaming(false);
1110
- setInput("");
1111
- return true;
1112
- }
1113
- // Add all changes
1114
- const addResult = await agent.executeBashCommand("git add .");
1115
- if (!addResult.success) {
1116
- const addErrorEntry = {
1117
- type: "assistant",
1118
- content: `Failed to stage changes: ${addResult.error || "Unknown error"}`,
1119
- timestamp: new Date(),
1120
- };
1121
- setChatHistory((prev) => [...prev, addErrorEntry]);
1122
- setIsProcessing(false);
1123
- setIsStreaming(false);
1124
- setInput("");
1125
- return true;
1126
- }
1127
- // Show that changes were staged
1128
- const addEntry = {
1129
- type: "tool_result",
1130
- content: "Changes staged successfully",
1131
- timestamp: new Date(),
1132
- toolCall: {
1133
- id: `git_add_${Date.now()}`,
1134
- type: "function",
1135
- function: {
1136
- name: "bash",
1137
- arguments: JSON.stringify({ command: "git add ." }),
1138
- },
1139
- },
1140
- toolResult: addResult,
1141
- };
1142
- setChatHistory((prev) => [...prev, addEntry]);
1143
- // Get staged changes for commit message generation
1144
- const diffResult = await agent.executeBashCommand("git diff --cached");
1145
- // Generate commit message using AI
1146
- const commitPrompt = `Generate a concise, professional git commit message for these changes:
1147
-
1148
- Git Status:
1149
- ${initialStatusResult.output}
1150
-
1151
- Git Diff (staged changes):
1152
- ${diffResult.output || "No staged changes shown"}
1153
-
1154
- Follow conventional commit format (feat:, fix:, docs:, etc.) and keep it under 72 characters.
1155
- Respond with ONLY the commit message, no additional text.`;
1156
- let commitMessage = "";
1157
- let streamingEntry = null;
1158
- for await (const chunk of agent.processUserMessageStream(commitPrompt)) {
1159
- if (chunk.type === "content" && chunk.content) {
1160
- if (!streamingEntry) {
1161
- const newEntry = {
1162
- type: "assistant",
1163
- content: `Generating commit message...\n\n${chunk.content}`,
1164
- timestamp: new Date(),
1165
- isStreaming: true,
1166
- };
1167
- setChatHistory((prev) => [...prev, newEntry]);
1168
- streamingEntry = newEntry;
1169
- commitMessage = chunk.content;
1170
- }
1171
- else {
1172
- commitMessage += chunk.content;
1173
- setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
1174
- ? {
1175
- ...entry,
1176
- content: `Generating commit message...\n\n${commitMessage}`,
1177
- }
1178
- : entry));
1179
- }
1180
- }
1181
- else if (chunk.type === "done") {
1182
- if (streamingEntry) {
1183
- setChatHistory((prev) => prev.map((entry) => entry.isStreaming
1184
- ? {
1185
- ...entry,
1186
- content: `Generated commit message: "${commitMessage.trim()}"`,
1187
- isStreaming: false,
1188
- }
1189
- : entry));
1190
- }
1191
- break;
1192
- }
1193
- }
1194
- // Execute the commit with properly escaped message to prevent command injection
1195
- const cleanCommitMessage = commitMessage.trim();
1196
- const commitCommand = `git commit -m ${escapeShellArg(cleanCommitMessage)}`;
1197
- const commitResult = await agent.executeBashCommand(commitCommand);
1198
- const commitEntry = {
1199
- type: "tool_result",
1200
- content: commitResult.success
1201
- ? commitResult.output || "Commit successful"
1202
- : commitResult.error || "Commit failed",
1203
- timestamp: new Date(),
1204
- toolCall: {
1205
- id: `git_commit_${Date.now()}`,
1206
- type: "function",
1207
- function: {
1208
- name: "bash",
1209
- arguments: JSON.stringify({ command: commitCommand }),
1210
- },
1211
- },
1212
- toolResult: commitResult,
1213
- };
1214
- setChatHistory((prev) => [...prev, commitEntry]);
1215
- // If commit was successful, push to remote
1216
- if (commitResult.success) {
1217
- // First try regular push, if it fails try with upstream setup
1218
- let pushResult = await agent.executeBashCommand("git push");
1219
- let pushCommand = "git push";
1220
- if (!pushResult.success &&
1221
- pushResult.error?.includes("no upstream branch")) {
1222
- pushCommand = "git push -u origin HEAD";
1223
- pushResult = await agent.executeBashCommand(pushCommand);
1224
- }
1225
- const pushEntry = {
1226
- type: "tool_result",
1227
- content: pushResult.success
1228
- ? pushResult.output || "Push successful"
1229
- : pushResult.error || "Push failed",
1230
- timestamp: new Date(),
1231
- toolCall: {
1232
- id: `git_push_${Date.now()}`,
1233
- type: "function",
1234
- function: {
1235
- name: "bash",
1236
- arguments: JSON.stringify({ command: pushCommand }),
1237
- },
1238
- },
1239
- toolResult: pushResult,
1240
- };
1241
- setChatHistory((prev) => [...prev, pushEntry]);
1242
- }
1243
- }
1244
- catch (error) {
1245
- const errorEntry = {
1246
- type: "assistant",
1247
- content: `Error during commit and push: ${error.message}`,
1248
- timestamp: new Date(),
1249
- };
1250
- setChatHistory((prev) => [...prev, errorEntry]);
1251
- }
1252
- setIsProcessing(false);
1253
- setIsStreaming(false);
1254
- clearInput();
1255
- return true;
1256
- }
1257
- const directBashCommands = [
1258
- "ls",
1259
- "pwd",
1260
- "cd",
1261
- "cat",
1262
- "mkdir",
1263
- "touch",
1264
- "echo",
1265
- "grep",
1266
- "find",
1267
- "cp",
1268
- "mv",
1269
- "rm",
1270
- ];
1271
- const firstWord = trimmedInput.split(" ")[0];
1272
- if (directBashCommands.includes(firstWord)) {
1273
- const userEntry = {
1274
- type: "user",
1275
- content: trimmedInput,
1276
- timestamp: new Date(),
1277
- };
1278
- setChatHistory((prev) => [...prev, userEntry]);
1279
- try {
1280
- const result = await agent.executeBashCommand(trimmedInput);
1281
- const commandEntry = {
1282
- type: "tool_result",
1283
- content: result.success
1284
- ? result.output || "Command completed"
1285
- : result.error || "Command failed",
1286
- timestamp: new Date(),
1287
- toolCall: {
1288
- id: `bash_${Date.now()}`,
1289
- type: "function",
1290
- function: {
1291
- name: "bash",
1292
- arguments: JSON.stringify({ command: trimmedInput }),
1293
- },
1294
- },
1295
- toolResult: result,
1296
- };
1297
- setChatHistory((prev) => [...prev, commandEntry]);
1298
- }
1299
- catch (error) {
1300
- const errorEntry = {
1301
- type: "assistant",
1302
- content: `Error executing command: ${error.message}`,
1303
- timestamp: new Date(),
1304
- };
1305
- setChatHistory((prev) => [...prev, errorEntry]);
1306
- }
1307
- clearInput();
1308
- return true;
1309
- }
1310
- return false;
1311
- };
1312
- const processUserMessage = async (userInput) => {
1313
- const userEntry = {
1314
- type: "user",
1315
- content: userInput,
1316
- timestamp: new Date(),
1317
- };
1318
- setChatHistory((prev) => [...prev, userEntry]);
1319
- setIsProcessing(true);
1320
- clearInput();
1321
- try {
1322
- setIsStreaming(true);
1323
- let streamingEntry = null;
1324
- for await (const chunk of agent.processUserMessageStream(userInput)) {
1325
- switch (chunk.type) {
1326
- case "content":
1327
- if (chunk.content) {
1328
- if (!streamingEntry) {
1329
- const newStreamingEntry = {
1330
- type: "assistant",
1331
- content: chunk.content,
1332
- timestamp: new Date(),
1333
- isStreaming: true,
1334
- };
1335
- setChatHistory((prev) => [...prev, newStreamingEntry]);
1336
- streamingEntry = newStreamingEntry;
1337
- }
1338
- else {
1339
- setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
1340
- ? { ...entry, content: entry.content + chunk.content }
1341
- : entry));
1342
- }
1343
- }
1344
- break;
1345
- case "token_count":
1346
- if (chunk.tokenCount !== undefined) {
1347
- setTokenCount(chunk.tokenCount);
1348
- }
1349
- break;
1350
- case "tool_calls":
1351
- if (chunk.toolCalls) {
1352
- // Stop streaming for the current assistant message
1353
- setChatHistory((prev) => prev.map((entry) => entry.isStreaming
1354
- ? {
1355
- ...entry,
1356
- isStreaming: false,
1357
- toolCalls: chunk.toolCalls,
1358
- }
1359
- : entry));
1360
- streamingEntry = null;
1361
- // Add individual tool call entries to show tools are being executed
1362
- chunk.toolCalls.forEach((toolCall) => {
1363
- const toolCallEntry = {
1364
- type: "tool_call",
1365
- content: "Executing...",
1366
- timestamp: new Date(),
1367
- toolCall: toolCall,
1368
- };
1369
- setChatHistory((prev) => [...prev, toolCallEntry]);
1370
- });
1371
- }
1372
- break;
1373
- case "tool_result":
1374
- if (chunk.toolCall && chunk.toolResult) {
1375
- setChatHistory((prev) => prev.map((entry) => {
1376
- if (entry.isStreaming) {
1377
- return { ...entry, isStreaming: false };
1378
- }
1379
- // Update the existing tool_call entry with the result
1380
- if (entry.type === "tool_call" &&
1381
- entry.toolCall?.id === chunk.toolCall?.id) {
1382
- return {
1383
- ...entry,
1384
- type: "tool_result",
1385
- content: chunk.toolResult?.success
1386
- ? chunk.toolResult?.output || "Success"
1387
- : chunk.toolResult?.error || "Error occurred",
1388
- toolResult: chunk.toolResult,
1389
- };
1390
- }
1391
- return entry;
1392
- }));
1393
- streamingEntry = null;
1394
- }
1395
- break;
1396
- case "done":
1397
- if (streamingEntry) {
1398
- setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false } : entry));
1399
- }
1400
- setIsStreaming(false);
1401
- break;
1402
- }
1403
- }
1404
- }
1405
- catch (error) {
1406
- const errorEntry = {
1407
- type: "assistant",
1408
- content: `Error: ${error.message}`,
1409
- timestamp: new Date(),
1410
- };
1411
- setChatHistory((prev) => [...prev, errorEntry]);
1412
- setIsStreaming(false);
1413
- }
1414
- setIsProcessing(false);
1415
- processingStartTime.current = 0;
1416
- };
1417
- return {
1418
- input,
1419
- cursorPosition,
1420
- showCommandSuggestions,
1421
- selectedCommandIndex,
1422
- commandSuggestions,
1423
- availableModels,
1424
- agent,
1425
- autoEditEnabled,
1426
- verboseMode,
1427
- backgroundMode,
1428
- };
1429
- }
1430
- //# sourceMappingURL=use-input-handler.js.map