wave-code 0.0.2

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 (131) hide show
  1. package/README.md +120 -0
  2. package/bin/wave-code.js +16 -0
  3. package/dist/cli.d.ts +6 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -0
  6. package/dist/components/App.d.ts +8 -0
  7. package/dist/components/App.d.ts.map +1 -0
  8. package/dist/components/App.js +10 -0
  9. package/dist/components/BashHistorySelector.d.ts +10 -0
  10. package/dist/components/BashHistorySelector.d.ts.map +1 -0
  11. package/dist/components/BashHistorySelector.js +83 -0
  12. package/dist/components/BashShellManager.d.ts +6 -0
  13. package/dist/components/BashShellManager.d.ts.map +1 -0
  14. package/dist/components/BashShellManager.js +116 -0
  15. package/dist/components/ChatInterface.d.ts +3 -0
  16. package/dist/components/ChatInterface.d.ts.map +1 -0
  17. package/dist/components/ChatInterface.js +31 -0
  18. package/dist/components/CommandOutputDisplay.d.ts +9 -0
  19. package/dist/components/CommandOutputDisplay.d.ts.map +1 -0
  20. package/dist/components/CommandOutputDisplay.js +40 -0
  21. package/dist/components/CommandSelector.d.ts +11 -0
  22. package/dist/components/CommandSelector.d.ts.map +1 -0
  23. package/dist/components/CommandSelector.js +60 -0
  24. package/dist/components/CompressDisplay.d.ts +9 -0
  25. package/dist/components/CompressDisplay.d.ts.map +1 -0
  26. package/dist/components/CompressDisplay.js +17 -0
  27. package/dist/components/DiffViewer.d.ts +9 -0
  28. package/dist/components/DiffViewer.d.ts.map +1 -0
  29. package/dist/components/DiffViewer.js +221 -0
  30. package/dist/components/FileSelector.d.ts +13 -0
  31. package/dist/components/FileSelector.d.ts.map +1 -0
  32. package/dist/components/FileSelector.js +48 -0
  33. package/dist/components/InputBox.d.ts +23 -0
  34. package/dist/components/InputBox.d.ts.map +1 -0
  35. package/dist/components/InputBox.js +124 -0
  36. package/dist/components/McpManager.d.ts +10 -0
  37. package/dist/components/McpManager.d.ts.map +1 -0
  38. package/dist/components/McpManager.js +123 -0
  39. package/dist/components/MemoryDisplay.d.ts +8 -0
  40. package/dist/components/MemoryDisplay.d.ts.map +1 -0
  41. package/dist/components/MemoryDisplay.js +25 -0
  42. package/dist/components/MemoryTypeSelector.d.ts +8 -0
  43. package/dist/components/MemoryTypeSelector.d.ts.map +1 -0
  44. package/dist/components/MemoryTypeSelector.js +38 -0
  45. package/dist/components/MessageList.d.ts +12 -0
  46. package/dist/components/MessageList.d.ts.map +1 -0
  47. package/dist/components/MessageList.js +36 -0
  48. package/dist/components/ToolResultDisplay.d.ts +9 -0
  49. package/dist/components/ToolResultDisplay.d.ts.map +1 -0
  50. package/dist/components/ToolResultDisplay.js +52 -0
  51. package/dist/contexts/useAppConfig.d.ts +11 -0
  52. package/dist/contexts/useAppConfig.d.ts.map +1 -0
  53. package/dist/contexts/useAppConfig.js +13 -0
  54. package/dist/contexts/useChat.d.ts +36 -0
  55. package/dist/contexts/useChat.d.ts.map +1 -0
  56. package/dist/contexts/useChat.js +208 -0
  57. package/dist/hooks/useBashHistorySelector.d.ts +15 -0
  58. package/dist/hooks/useBashHistorySelector.d.ts.map +1 -0
  59. package/dist/hooks/useBashHistorySelector.js +61 -0
  60. package/dist/hooks/useCommandSelector.d.ts +24 -0
  61. package/dist/hooks/useCommandSelector.d.ts.map +1 -0
  62. package/dist/hooks/useCommandSelector.js +98 -0
  63. package/dist/hooks/useFileSelector.d.ts +16 -0
  64. package/dist/hooks/useFileSelector.d.ts.map +1 -0
  65. package/dist/hooks/useFileSelector.js +174 -0
  66. package/dist/hooks/useImageManager.d.ts +13 -0
  67. package/dist/hooks/useImageManager.d.ts.map +1 -0
  68. package/dist/hooks/useImageManager.js +46 -0
  69. package/dist/hooks/useInputHistory.d.ts +11 -0
  70. package/dist/hooks/useInputHistory.d.ts.map +1 -0
  71. package/dist/hooks/useInputHistory.js +64 -0
  72. package/dist/hooks/useInputKeyboardHandler.d.ts +83 -0
  73. package/dist/hooks/useInputKeyboardHandler.d.ts.map +1 -0
  74. package/dist/hooks/useInputKeyboardHandler.js +507 -0
  75. package/dist/hooks/useInputState.d.ts +14 -0
  76. package/dist/hooks/useInputState.d.ts.map +1 -0
  77. package/dist/hooks/useInputState.js +57 -0
  78. package/dist/hooks/useMemoryTypeSelector.d.ts +9 -0
  79. package/dist/hooks/useMemoryTypeSelector.d.ts.map +1 -0
  80. package/dist/hooks/useMemoryTypeSelector.js +27 -0
  81. package/dist/hooks/usePagination.d.ts +20 -0
  82. package/dist/hooks/usePagination.d.ts.map +1 -0
  83. package/dist/hooks/usePagination.js +168 -0
  84. package/dist/index.d.ts +5 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +91 -0
  87. package/dist/plain-cli.d.ts +7 -0
  88. package/dist/plain-cli.d.ts.map +1 -0
  89. package/dist/plain-cli.js +49 -0
  90. package/dist/utils/clipboard.d.ts +22 -0
  91. package/dist/utils/clipboard.d.ts.map +1 -0
  92. package/dist/utils/clipboard.js +347 -0
  93. package/dist/utils/constants.d.ts +17 -0
  94. package/dist/utils/constants.d.ts.map +1 -0
  95. package/dist/utils/constants.js +18 -0
  96. package/dist/utils/logger.d.ts +72 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +245 -0
  99. package/package.json +60 -0
  100. package/src/cli.tsx +82 -0
  101. package/src/components/App.tsx +31 -0
  102. package/src/components/BashHistorySelector.tsx +163 -0
  103. package/src/components/BashShellManager.tsx +306 -0
  104. package/src/components/ChatInterface.tsx +88 -0
  105. package/src/components/CommandOutputDisplay.tsx +81 -0
  106. package/src/components/CommandSelector.tsx +144 -0
  107. package/src/components/CompressDisplay.tsx +58 -0
  108. package/src/components/DiffViewer.tsx +321 -0
  109. package/src/components/FileSelector.tsx +137 -0
  110. package/src/components/InputBox.tsx +310 -0
  111. package/src/components/McpManager.tsx +328 -0
  112. package/src/components/MemoryDisplay.tsx +62 -0
  113. package/src/components/MemoryTypeSelector.tsx +96 -0
  114. package/src/components/MessageList.tsx +215 -0
  115. package/src/components/ToolResultDisplay.tsx +138 -0
  116. package/src/contexts/useAppConfig.tsx +32 -0
  117. package/src/contexts/useChat.tsx +300 -0
  118. package/src/hooks/useBashHistorySelector.ts +77 -0
  119. package/src/hooks/useCommandSelector.ts +131 -0
  120. package/src/hooks/useFileSelector.ts +227 -0
  121. package/src/hooks/useImageManager.ts +64 -0
  122. package/src/hooks/useInputHistory.ts +74 -0
  123. package/src/hooks/useInputKeyboardHandler.ts +778 -0
  124. package/src/hooks/useInputState.ts +66 -0
  125. package/src/hooks/useMemoryTypeSelector.ts +40 -0
  126. package/src/hooks/usePagination.ts +203 -0
  127. package/src/index.ts +108 -0
  128. package/src/plain-cli.ts +66 -0
  129. package/src/utils/clipboard.ts +384 -0
  130. package/src/utils/constants.ts +22 -0
  131. package/src/utils/logger.ts +301 -0
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # Wave Code
2
+
3
+ CLI-based code assistant powered by AI, built with React and Ink.
4
+
5
+ This is the frontend UI package of the Wave workspace, providing an interactive command-line interface for AI-powered development assistance.
6
+
7
+ ## Features
8
+
9
+ - Interactive CLI interface built with React and Ink
10
+ - Real-time chat interface with AI assistant
11
+ - File browser and editor integration
12
+ - Command execution and output display
13
+ - Session management and restoration
14
+ - Memory context management
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install wave-code
20
+ ```
21
+
22
+ ## Environment Configuration
23
+
24
+ Before use, you need to configure the following environment variables for AI model authentication:
25
+
26
+ ### Required Environment Variables
27
+
28
+ ```bash
29
+ # AI Gateway access token (required)
30
+ export AIGW_TOKEN="your_token_here"
31
+
32
+ # AI Gateway API URL (required)
33
+ export AIGW_URL="https://your-api-gateway-url.com"
34
+ ```
35
+
36
+ ### Optional Environment Variables
37
+
38
+ ```bash
39
+ # Specify AI model (optional, defaults to system configured model)
40
+ export AIGW_MODEL="gemini-2.5-flash"
41
+
42
+ # Specify fast AI model (optional, for quick response scenarios)
43
+ export AIGW_FAST_MODEL="gemini-1.5-flash"
44
+
45
+ # Log level (optional, defaults to info)
46
+ export LOG_LEVEL="debug"
47
+
48
+ # Log file path (optional)
49
+ export LOG_FILE="/path/to/your/logfile.log"
50
+
51
+ # Maximum log file size (optional, defaults to 10MB)
52
+ export LOG_MAX_FILE_SIZE="10485760"
53
+
54
+ # Token limit (optional, defaults to 64000)
55
+ export TOKEN_LIMIT="64000"
56
+
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ### Full Command
62
+
63
+ ```bash
64
+ # Start the CLI
65
+ wave-code
66
+
67
+ # Continue from last session
68
+ wave-code --continue
69
+
70
+ # Restore specific session
71
+ wave-code --restore session_id
72
+
73
+ # List available sessions
74
+ wave-code --list-sessions
75
+ ```
76
+
77
+ ### Short Command (wave)
78
+
79
+ For convenience, you can also use the shorter `wave` command:
80
+
81
+ ```bash
82
+ # Start the CLI (equivalent to wave-code)
83
+ wave
84
+
85
+ # Continue from last session
86
+ wave --continue
87
+
88
+ # Restore specific session
89
+ wave --restore session_id
90
+
91
+ # List available sessions
92
+ wave --list-sessions
93
+
94
+ # Show help
95
+ wave --help
96
+ ```
97
+
98
+ The `wave` command is an alias for `wave-code` and supports all the same options and functionality.
99
+
100
+ ## Development
101
+
102
+ This package depends on `wave-agent-sdk` for core functionality including AI services, tools, and utilities.
103
+
104
+ ```bash
105
+ # Install dependencies
106
+ pnpm install
107
+
108
+ # Start development
109
+ pnpm dev
110
+
111
+ # Build
112
+ pnpm build
113
+
114
+ # Test
115
+ pnpm test
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Import and start the CLI
4
+ import("../dist/index.js")
5
+ .then(async ({ main }) => {
6
+ try {
7
+ await main();
8
+ } catch (err) {
9
+ console.error("Failed to start CLI:", err);
10
+ process.exit(1);
11
+ }
12
+ })
13
+ .catch((err) => {
14
+ console.error("Failed to import CLI module:", err);
15
+ process.exit(1);
16
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export interface CliOptions {
2
+ restoreSessionId?: string;
3
+ continueLastSession?: boolean;
4
+ }
5
+ export declare function startCli(options: CliOptions): Promise<void>;
6
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEjE"}
package/dist/cli.js ADDED
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from "ink";
3
+ import { App } from "./components/App.js";
4
+ import { cleanupLogs } from "./utils/logger.js";
5
+ export async function startCli(options) {
6
+ const { restoreSessionId, continueLastSession } = options;
7
+ // Continue with ink-based UI for normal mode
8
+ // Global cleanup tracker
9
+ let isCleaningUp = false;
10
+ let appUnmounted = false;
11
+ const cleanup = async () => {
12
+ if (isCleaningUp)
13
+ return;
14
+ isCleaningUp = true;
15
+ console.log("\nShutting down gracefully...");
16
+ try {
17
+ // Clean up old log files
18
+ await cleanupLogs().catch((error) => {
19
+ console.warn("Failed to cleanup old logs:", error);
20
+ });
21
+ // Unmount the React app to trigger cleanup
22
+ if (!appUnmounted) {
23
+ unmount();
24
+ appUnmounted = true;
25
+ // Give React time to cleanup
26
+ await new Promise((resolve) => setTimeout(resolve, 100));
27
+ }
28
+ process.exit(0);
29
+ }
30
+ catch (error) {
31
+ console.error("Error during cleanup:", error);
32
+ process.exit(1);
33
+ }
34
+ };
35
+ // Handle process signals
36
+ process.on("SIGINT", cleanup);
37
+ process.on("SIGTERM", cleanup);
38
+ // Handle uncaught exceptions
39
+ process.on("uncaughtException", (error) => {
40
+ console.error("Uncaught exception:", error);
41
+ cleanup();
42
+ });
43
+ process.on("unhandledRejection", (reason, promise) => {
44
+ console.error("Unhandled rejection at:", promise, "reason:", reason);
45
+ cleanup();
46
+ });
47
+ // Render the application
48
+ const { unmount } = render(_jsx(App, { restoreSessionId: restoreSessionId, continueLastSession: continueLastSession }));
49
+ // Store unmount function for cleanup when process exits normally
50
+ process.on("exit", () => {
51
+ if (!appUnmounted) {
52
+ try {
53
+ unmount();
54
+ }
55
+ catch {
56
+ // Ignore errors during unmount
57
+ }
58
+ }
59
+ });
60
+ // Return a promise that never resolves to keep the CLI running
61
+ return new Promise(() => { });
62
+ }
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ interface AppProps {
3
+ restoreSessionId?: string;
4
+ continueLastSession?: boolean;
5
+ }
6
+ export declare const App: React.FC<AppProps>;
7
+ export {};
8
+ //# sourceMappingURL=App.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/components/App.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,UAAU,QAAQ;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAUD,eAAO,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAYlC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ChatInterface } from "./ChatInterface.js";
3
+ import { ChatProvider } from "../contexts/useChat.js";
4
+ import { AppProvider } from "../contexts/useAppConfig.js";
5
+ const AppWithProviders = () => {
6
+ return (_jsx(ChatProvider, { children: _jsx(ChatInterface, {}) }));
7
+ };
8
+ export const App = ({ restoreSessionId, continueLastSession, }) => {
9
+ return (_jsx(AppProvider, { restoreSessionId: restoreSessionId, continueLastSession: continueLastSession, children: _jsx(AppWithProviders, {}) }));
10
+ };
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ export interface BashHistorySelectorProps {
3
+ searchQuery: string;
4
+ workdir: string;
5
+ onSelect: (command: string) => void;
6
+ onExecute: (command: string) => void;
7
+ onCancel: () => void;
8
+ }
9
+ export declare const BashHistorySelector: React.FC<BashHistorySelectorProps>;
10
+ //# sourceMappingURL=BashHistorySelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BashHistorySelector.d.ts","sourceRoot":"","sources":["../../src/components/BashHistorySelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAKnD,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAqJlE,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { searchBashHistory } from "wave-agent-sdk";
5
+ import { logger } from "../utils/logger.js";
6
+ export const BashHistorySelector = ({ searchQuery, workdir, onSelect, onExecute, onCancel, }) => {
7
+ const [selectedIndex, setSelectedIndex] = useState(0);
8
+ const [commands, setCommands] = useState([]);
9
+ // Search bash history
10
+ useEffect(() => {
11
+ const results = searchBashHistory(searchQuery, 10, workdir);
12
+ setCommands(results);
13
+ setSelectedIndex(0);
14
+ logger.debug("Bash history search:", {
15
+ searchQuery,
16
+ workdir,
17
+ resultCount: results.length,
18
+ });
19
+ }, [searchQuery, workdir]);
20
+ useInput((input, key) => {
21
+ logger.debug("BashHistorySelector useInput:", {
22
+ input,
23
+ key,
24
+ commandsLength: commands.length,
25
+ selectedIndex,
26
+ });
27
+ if (key.return) {
28
+ if (commands.length > 0 && selectedIndex < commands.length) {
29
+ const selectedCommand = commands[selectedIndex];
30
+ onExecute(selectedCommand.command);
31
+ }
32
+ else if (commands.length === 0 && searchQuery.trim()) {
33
+ // When no history records match, execute the search query as a new command
34
+ onExecute(searchQuery.trim());
35
+ }
36
+ return;
37
+ }
38
+ if (key.tab) {
39
+ if (commands.length > 0 && selectedIndex < commands.length) {
40
+ const selectedCommand = commands[selectedIndex];
41
+ onSelect(selectedCommand.command);
42
+ }
43
+ else if (commands.length === 0 && searchQuery.trim()) {
44
+ // When no history records match, insert the search query
45
+ onSelect(searchQuery.trim());
46
+ }
47
+ return;
48
+ }
49
+ if (key.escape) {
50
+ onCancel();
51
+ return;
52
+ }
53
+ if (key.upArrow) {
54
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
55
+ return;
56
+ }
57
+ if (key.downArrow) {
58
+ setSelectedIndex(Math.min(commands.length - 1, selectedIndex + 1));
59
+ return;
60
+ }
61
+ });
62
+ if (commands.length === 0) {
63
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", padding: 1, marginBottom: 1, children: [_jsxs(Text, { color: "yellow", children: ["No bash history found ", searchQuery && `for "${searchQuery}"`] }), searchQuery.trim() && (_jsxs(Text, { color: "green", children: ["Press Enter to execute: ", searchQuery] })), searchQuery.trim() && (_jsxs(Text, { color: "blue", children: ["Press Tab to insert: ", searchQuery] })), _jsx(Text, { dimColor: true, children: "Press Escape to cancel" })] }));
64
+ }
65
+ const formatTimestamp = (timestamp) => {
66
+ const date = new Date(timestamp);
67
+ const now = new Date();
68
+ const diffMs = now.getTime() - date.getTime();
69
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
70
+ const diffDays = Math.floor(diffHours / 24);
71
+ if (diffDays > 0) {
72
+ return `${diffDays}d ago`;
73
+ }
74
+ else if (diffHours > 0) {
75
+ return `${diffHours}h ago`;
76
+ }
77
+ else {
78
+ const diffMinutes = Math.floor(diffMs / (1000 * 60));
79
+ return diffMinutes > 0 ? `${diffMinutes}m ago` : "just now";
80
+ }
81
+ };
82
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "blue", padding: 1, gap: 1, marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "blue", bold: true, children: ["Bash History ", searchQuery && `(filtering: "${searchQuery}")`] }) }), commands.map((cmd, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: index === selectedIndex ? "black" : "white", backgroundColor: index === selectedIndex ? "blue" : undefined, children: cmd.command }), index === selectedIndex && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: _jsxs(Text, { color: "gray", dimColor: true, children: [formatTimestamp(cmd.timestamp), cmd.workdir !== workdir && ` • in ${cmd.workdir}`] }) }))] }, index))), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate, Enter to execute, Tab to insert, Escape to cancel" }) })] }));
83
+ };
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ export interface BashShellManagerProps {
3
+ onCancel: () => void;
4
+ }
5
+ export declare const BashShellManager: React.FC<BashShellManagerProps>;
6
+ //# sourceMappingURL=BashShellManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BashShellManager.d.ts","sourceRoot":"","sources":["../../src/components/BashShellManager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAanD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAgS5D,CAAC"}
@@ -0,0 +1,116 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { useChat } from "../contexts/useChat.js";
5
+ export const BashShellManager = ({ onCancel, }) => {
6
+ const { backgroundShells, getBackgroundShellOutput, killBackgroundShell } = useChat();
7
+ const [shells, setShells] = useState([]);
8
+ const [selectedIndex, setSelectedIndex] = useState(0);
9
+ const [viewMode, setViewMode] = useState("list");
10
+ const [detailShellId, setDetailShellId] = useState(null);
11
+ const [detailOutput, setDetailOutput] = useState(null);
12
+ // Convert backgroundShells to local BashShell format
13
+ useEffect(() => {
14
+ setShells(backgroundShells.map((shell) => ({
15
+ id: shell.id,
16
+ command: shell.command,
17
+ status: shell.status,
18
+ startTime: shell.startTime,
19
+ exitCode: shell.exitCode,
20
+ runtime: shell.runtime,
21
+ })));
22
+ }, [backgroundShells]);
23
+ // Load detail output for selected shell
24
+ useEffect(() => {
25
+ if (viewMode === "detail" && detailShellId) {
26
+ const output = getBackgroundShellOutput(detailShellId);
27
+ setDetailOutput(output);
28
+ }
29
+ }, [viewMode, detailShellId, getBackgroundShellOutput]);
30
+ const formatDuration = (ms) => {
31
+ if (ms < 1000)
32
+ return `${ms}ms`;
33
+ if (ms < 60000)
34
+ return `${Math.round(ms / 1000)}s`;
35
+ const minutes = Math.floor(ms / 60000);
36
+ const seconds = Math.round((ms % 60000) / 1000);
37
+ return `${minutes}m ${seconds}s`;
38
+ };
39
+ const formatTime = (timestamp) => {
40
+ return new Date(timestamp).toLocaleTimeString();
41
+ };
42
+ const killShell = (shellId) => {
43
+ killBackgroundShell(shellId);
44
+ };
45
+ useInput((input, key) => {
46
+ if (viewMode === "list") {
47
+ // List mode navigation
48
+ if (key.return) {
49
+ if (shells.length > 0 && selectedIndex < shells.length) {
50
+ const selectedShell = shells[selectedIndex];
51
+ setDetailShellId(selectedShell.id);
52
+ setViewMode("detail");
53
+ }
54
+ return;
55
+ }
56
+ if (key.escape) {
57
+ onCancel();
58
+ return;
59
+ }
60
+ if (key.upArrow) {
61
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
62
+ return;
63
+ }
64
+ if (key.downArrow) {
65
+ setSelectedIndex(Math.min(shells.length - 1, selectedIndex + 1));
66
+ return;
67
+ }
68
+ if (input === "k" && shells.length > 0 && selectedIndex < shells.length) {
69
+ const selectedShell = shells[selectedIndex];
70
+ if (selectedShell.status === "running") {
71
+ killShell(selectedShell.id);
72
+ }
73
+ return;
74
+ }
75
+ }
76
+ else if (viewMode === "detail") {
77
+ // Detail mode navigation
78
+ if (key.escape) {
79
+ setViewMode("list");
80
+ setDetailShellId(null);
81
+ setDetailOutput(null);
82
+ return;
83
+ }
84
+ if (input === "k" && detailShellId) {
85
+ const shell = shells.find((s) => s.id === detailShellId);
86
+ if (shell && shell.status === "running") {
87
+ killShell(detailShellId);
88
+ }
89
+ return;
90
+ }
91
+ }
92
+ });
93
+ if (viewMode === "detail" && detailShellId && detailOutput) {
94
+ const shell = shells.find((s) => s.id === detailShellId);
95
+ if (!shell) {
96
+ setViewMode("list");
97
+ return null;
98
+ }
99
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", padding: 1, gap: 1, marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "cyan", bold: true, children: ["Background Shell Details: ", shell.id] }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Command:" }), " ", shell.command] }) }), _jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Status:" }), " ", shell.status, shell.exitCode !== undefined &&
100
+ ` (exit code: ${shell.exitCode})`] }) }), _jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Started:" }), " ", formatTime(shell.startTime), shell.runtime !== undefined && (_jsxs(Text, { children: [" ", "| ", _jsx(Text, { color: "blue", children: "Runtime:" }), " ", formatDuration(shell.runtime)] }))] }) })] }), detailOutput.stdout && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "green", bold: true, children: "STDOUT (last 10 lines):" }), _jsx(Box, { borderStyle: "single", borderColor: "green", padding: 1, children: _jsx(Text, { children: detailOutput.stdout.split("\n").slice(-10).join("\n") }) })] })), detailOutput.stderr && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "red", bold: true, children: "STDERR:" }), _jsx(Box, { borderStyle: "single", borderColor: "red", padding: 1, children: _jsx(Text, { color: "red", children: detailOutput.stderr.split("\n").slice(-10).join("\n") }) })] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [shell.status === "running" ? "k to kill · " : "", "Esc to go back"] }) })] }));
101
+ }
102
+ if (!backgroundShells) {
103
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", padding: 1, marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "Background Bash Shells" }), _jsx(Text, { children: "Background bash shells not available" }), _jsx(Text, { dimColor: true, children: "Press Escape to close" })] }));
104
+ }
105
+ if (shells.length === 0) {
106
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", padding: 1, marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "Background Bash Shells" }), _jsx(Text, { children: "No background shells found" }), _jsx(Text, { dimColor: true, children: "Press Escape to close" })] }));
107
+ }
108
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", padding: 1, gap: 1, marginBottom: 1, children: [_jsx(Box, { children: _jsx(Text, { color: "cyan", bold: true, children: "Background Bash Shells" }) }), _jsx(Text, { dimColor: true, children: "Select a shell to view details" }), shells.map((shell, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: index === selectedIndex ? "black" : "white", backgroundColor: index === selectedIndex ? "cyan" : undefined, children: [index === selectedIndex ? "▶ " : " ", index + 1, ".", " ", shell.command.length > 50
109
+ ? shell.command.substring(0, 47) + "..."
110
+ : shell.command, _jsxs(Text, { color: shell.status === "running"
111
+ ? "green"
112
+ : shell.status === "completed"
113
+ ? "blue"
114
+ : "red", children: [" ", "(", shell.status, ")"] })] }), index === selectedIndex && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: _jsxs(Text, { color: "gray", dimColor: true, children: ["ID: ", shell.id, " | Started: ", formatTime(shell.startTime), shell.runtime !== undefined &&
115
+ ` | Runtime: ${formatDuration(shell.runtime)}`, shell.exitCode !== undefined && ` | Exit: ${shell.exitCode}`] }) }))] }, shell.id))), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["\u2191/\u2193 to select \u00B7 Enter to view \u00B7", " ", shells[selectedIndex]?.status === "running" ? "k to kill · " : "", "Esc to close"] }) })] }));
116
+ };
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ export declare const ChatInterface: React.FC;
3
+ //# sourceMappingURL=ChatInterface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAOjD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAgFjC,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useEffect } from "react";
3
+ import { Box } from "ink";
4
+ import { MessageList } from "./MessageList.js";
5
+ import { InputBox } from "./InputBox.js";
6
+ import { useChat } from "../contexts/useChat.js";
7
+ export const ChatInterface = () => {
8
+ const { messages, isLoading, isCommandRunning, userInputHistory, isCompressing, sendMessage, abortMessage, saveMemory, mcpServers, connectMcpServer, disconnectMcpServer, isExpanded, latestTotalTokens, slashCommands, hasSlashCommand, } = useChat();
9
+ // Create a ref to store messages in expanded mode
10
+ const expandedMessagesRef = useRef([]);
11
+ useEffect(() => {
12
+ // Only sync when collapsed
13
+ if (!isExpanded) {
14
+ expandedMessagesRef.current = messages.map((message, index) => {
15
+ // If it's the last message, deep copy its blocks
16
+ if (index === messages.length - 1) {
17
+ return {
18
+ ...message,
19
+ blocks: message.blocks.map((block) => ({ ...block })),
20
+ };
21
+ }
22
+ return message;
23
+ });
24
+ }
25
+ }, [isExpanded, messages]);
26
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Box, { flexGrow: 1, flexDirection: "column", paddingX: 1, children: isExpanded ? (
27
+ // Expanded mode uses messages from ref, loading and tokens are hardcoded to false and 0
28
+ _jsx(MessageList, { messages: expandedMessagesRef.current, isLoading: false, isCommandRunning: false, latestTotalTokens: 0, isExpanded: true })) : (
29
+ // Normal mode uses real-time state
30
+ _jsx(MessageList, { messages: messages, isLoading: isLoading, isCommandRunning: isCommandRunning, isCompressing: isCompressing, latestTotalTokens: latestTotalTokens, isExpanded: false })) }), !isExpanded && (_jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, userInputHistory: userInputHistory, sendMessage: sendMessage, abortMessage: abortMessage, saveMemory: saveMemory, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand }))] }));
31
+ };
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import type { CommandOutputBlock } from "wave-agent-sdk";
3
+ interface CommandOutputDisplayProps {
4
+ block: CommandOutputBlock;
5
+ isExpanded?: boolean;
6
+ }
7
+ export declare const CommandOutputDisplay: React.FC<CommandOutputDisplayProps>;
8
+ export {};
9
+ //# sourceMappingURL=CommandOutputDisplay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommandOutputDisplay.d.ts","sourceRoot":"","sources":["../../src/components/CommandOutputDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,UAAU,yBAAyB;IACjC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAuEpE,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Box, Text } from "ink";
4
+ export const CommandOutputDisplay = ({ block, isExpanded = false, }) => {
5
+ const { command, output, isRunning, exitCode } = block;
6
+ const [isOverflowing, setIsOverflowing] = useState(false);
7
+ const MAX_LINES = 10; // Set maximum display lines
8
+ // Detect if content is overflowing
9
+ useEffect(() => {
10
+ if (output) {
11
+ const lines = output.split("\n");
12
+ setIsOverflowing(!isExpanded && lines.length > MAX_LINES);
13
+ }
14
+ }, [output, isExpanded]);
15
+ const getStatusColor = () => {
16
+ if (isRunning)
17
+ return "yellow";
18
+ if (exitCode === 0)
19
+ return "green";
20
+ if (exitCode !== null && exitCode !== 0)
21
+ return "red";
22
+ return "gray"; // Unknown state
23
+ };
24
+ const getStatusText = () => {
25
+ if (isRunning)
26
+ return "🔄";
27
+ if (exitCode === 0)
28
+ return "✅";
29
+ if (exitCode === 130)
30
+ return "⚠️"; // SIGINT (Ctrl+C)
31
+ if (exitCode !== null && exitCode !== 0)
32
+ return "❌";
33
+ return ""; // Don't display text for unknown state
34
+ };
35
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "$ " }), _jsx(Text, { color: "white", children: command }), _jsxs(Text, { color: getStatusColor(), children: [" ", getStatusText()] })] }), output && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "gray", flexDirection: "column", height: isExpanded
36
+ ? undefined
37
+ : Math.min(output.split("\n").length, MAX_LINES), overflow: "hidden", children: _jsx(Text, { color: "gray", children: isOverflowing
38
+ ? output.split("\n").slice(-MAX_LINES).join("\n")
39
+ : output }) }), isOverflowing && (_jsx(Box, { paddingLeft: 2, marginTop: 1, children: _jsxs(Text, { color: "yellow", dimColor: true, children: ["Content truncated (", output.split("\n").length, " lines total, showing last ", MAX_LINES, " lines)"] }) }))] }))] }));
40
+ };
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import type { SlashCommand } from "wave-agent-sdk";
3
+ export interface CommandSelectorProps {
4
+ searchQuery: string;
5
+ onSelect: (command: string) => void;
6
+ onInsert?: (command: string) => void;
7
+ onCancel: () => void;
8
+ commands?: SlashCommand[];
9
+ }
10
+ export declare const CommandSelector: React.FC<CommandSelectorProps>;
11
+ //# sourceMappingURL=CommandSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommandSelector.d.ts","sourceRoot":"","sources":["../../src/components/CommandSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAiBnD,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAoH1D,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ const AVAILABLE_COMMANDS = [
5
+ {
6
+ id: "bashes",
7
+ name: "bashes",
8
+ description: "View and manage background bash shells",
9
+ handler: () => { }, // Handler here won't be used, actual processing is in the hook
10
+ },
11
+ {
12
+ id: "mcp",
13
+ name: "mcp",
14
+ description: "View and manage MCP servers",
15
+ handler: () => { }, // Handler here won't be used, actual processing is in the hook
16
+ },
17
+ ];
18
+ export const CommandSelector = ({ searchQuery, onSelect, onInsert, onCancel, commands = [], // Default to empty array
19
+ }) => {
20
+ const [selectedIndex, setSelectedIndex] = useState(0);
21
+ // Merge agent commands and local commands
22
+ const allCommands = [...commands, ...AVAILABLE_COMMANDS];
23
+ // Filter command list
24
+ const filteredCommands = allCommands.filter((command) => !searchQuery ||
25
+ command.name.toLowerCase().includes(searchQuery.toLowerCase()));
26
+ useInput((input, key) => {
27
+ if (key.return) {
28
+ if (filteredCommands.length > 0 &&
29
+ selectedIndex < filteredCommands.length) {
30
+ const selectedCommand = filteredCommands[selectedIndex].name;
31
+ onSelect(selectedCommand);
32
+ }
33
+ return;
34
+ }
35
+ if (key.tab && onInsert) {
36
+ if (filteredCommands.length > 0 &&
37
+ selectedIndex < filteredCommands.length) {
38
+ const selectedCommand = filteredCommands[selectedIndex].name;
39
+ onInsert(selectedCommand);
40
+ }
41
+ return;
42
+ }
43
+ if (key.escape) {
44
+ onCancel();
45
+ return;
46
+ }
47
+ if (key.upArrow) {
48
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
49
+ return;
50
+ }
51
+ if (key.downArrow) {
52
+ setSelectedIndex(Math.min(filteredCommands.length - 1, selectedIndex + 1));
53
+ return;
54
+ }
55
+ });
56
+ if (filteredCommands.length === 0) {
57
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", padding: 1, marginBottom: 1, children: [_jsxs(Text, { color: "yellow", children: ["No commands found for \"", searchQuery, "\""] }), _jsx(Text, { dimColor: true, children: "Press Escape to cancel" })] }));
58
+ }
59
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "magenta", padding: 1, gap: 1, marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "magenta", bold: true, children: ["Command Selector ", searchQuery && `(filtering: "${searchQuery}")`] }) }), filteredCommands.map((command, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: index === selectedIndex ? "black" : "white", backgroundColor: index === selectedIndex ? "magenta" : undefined, children: [index === selectedIndex ? "▶ " : " ", "/", command.name] }), index === selectedIndex && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { color: "gray", dimColor: true, children: command.description }) }))] }, command.name))), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: ["\u2191\u2193 navigate \u2022 Enter execute \u2022 ", onInsert ? "Tab insert • " : "", "Esc cancel"] }) })] }));
60
+ };
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import type { CompressBlock } from "wave-agent-sdk";
3
+ interface CompressDisplayProps {
4
+ block: CompressBlock;
5
+ isExpanded?: boolean;
6
+ }
7
+ export declare const CompressDisplay: React.FC<CompressDisplayProps>;
8
+ export {};
9
+ //# sourceMappingURL=CompressDisplay.d.ts.map