wave-code 0.7.2 → 0.8.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 (102) hide show
  1. package/dist/cli.d.ts +2 -4
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +24 -52
  4. package/dist/components/App.d.ts +3 -4
  5. package/dist/components/App.d.ts.map +1 -1
  6. package/dist/components/App.js +49 -6
  7. package/dist/components/BangDisplay.d.ts +9 -0
  8. package/dist/components/BangDisplay.d.ts.map +1 -0
  9. package/dist/components/{CommandOutputDisplay.js → BangDisplay.js} +1 -1
  10. package/dist/components/ChatInterface.d.ts.map +1 -1
  11. package/dist/components/ChatInterface.js +3 -2
  12. package/dist/components/CommandSelector.d.ts.map +1 -1
  13. package/dist/components/CommandSelector.js +18 -2
  14. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  15. package/dist/components/ConfirmationSelector.js +105 -8
  16. package/dist/components/HelpView.d.ts.map +1 -1
  17. package/dist/components/HelpView.js +2 -0
  18. package/dist/components/InputBox.d.ts.map +1 -1
  19. package/dist/components/InputBox.js +9 -3
  20. package/dist/components/MarketplaceAddForm.d.ts.map +1 -1
  21. package/dist/components/MarketplaceAddForm.js +13 -6
  22. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  23. package/dist/components/MarketplaceDetail.js +8 -3
  24. package/dist/components/MessageBlockItem.js +2 -2
  25. package/dist/components/MessageList.d.ts +4 -1
  26. package/dist/components/MessageList.d.ts.map +1 -1
  27. package/dist/components/MessageList.js +15 -8
  28. package/dist/components/PluginDetail.d.ts.map +1 -1
  29. package/dist/components/PluginDetail.js +14 -3
  30. package/dist/components/PluginManagerShell.d.ts.map +1 -1
  31. package/dist/components/PluginManagerShell.js +3 -3
  32. package/dist/components/PluginManagerTypes.d.ts +2 -0
  33. package/dist/components/PluginManagerTypes.d.ts.map +1 -1
  34. package/dist/components/SessionSelector.d.ts.map +1 -1
  35. package/dist/components/SessionSelector.js +5 -5
  36. package/dist/components/StatusCommand.d.ts +6 -0
  37. package/dist/components/StatusCommand.d.ts.map +1 -0
  38. package/dist/components/StatusCommand.js +28 -0
  39. package/dist/components/WorktreeExitPrompt.d.ts +13 -0
  40. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -0
  41. package/dist/components/WorktreeExitPrompt.js +26 -0
  42. package/dist/contexts/useChat.d.ts +9 -5
  43. package/dist/contexts/useChat.d.ts.map +1 -1
  44. package/dist/contexts/useChat.js +38 -8
  45. package/dist/contracts/status.d.ts +8 -0
  46. package/dist/contracts/status.d.ts.map +1 -0
  47. package/dist/contracts/status.js +1 -0
  48. package/dist/hooks/useInputManager.d.ts +2 -0
  49. package/dist/hooks/useInputManager.d.ts.map +1 -1
  50. package/dist/hooks/useInputManager.js +12 -0
  51. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  52. package/dist/hooks/usePluginManager.js +41 -13
  53. package/dist/hooks/useTasks.js +2 -2
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +53 -4
  56. package/dist/managers/InputManager.d.ts +6 -0
  57. package/dist/managers/InputManager.d.ts.map +1 -1
  58. package/dist/managers/InputManager.js +32 -13
  59. package/dist/print-cli.d.ts +2 -4
  60. package/dist/print-cli.d.ts.map +1 -1
  61. package/dist/print-cli.js +31 -2
  62. package/dist/session-selector-cli.d.ts +3 -1
  63. package/dist/session-selector-cli.d.ts.map +1 -1
  64. package/dist/session-selector-cli.js +2 -2
  65. package/dist/types.d.ts +11 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +1 -0
  68. package/dist/utils/worktree.d.ts +23 -0
  69. package/dist/utils/worktree.d.ts.map +1 -0
  70. package/dist/utils/worktree.js +135 -0
  71. package/package.json +2 -2
  72. package/src/cli.tsx +36 -59
  73. package/src/components/App.tsx +99 -11
  74. package/src/components/{CommandOutputDisplay.tsx → BangDisplay.tsx} +4 -4
  75. package/src/components/ChatInterface.tsx +8 -0
  76. package/src/components/CommandSelector.tsx +18 -1
  77. package/src/components/ConfirmationSelector.tsx +118 -9
  78. package/src/components/HelpView.tsx +2 -0
  79. package/src/components/InputBox.tsx +11 -1
  80. package/src/components/MarketplaceAddForm.tsx +21 -8
  81. package/src/components/MarketplaceDetail.tsx +19 -4
  82. package/src/components/MessageBlockItem.tsx +3 -3
  83. package/src/components/MessageList.tsx +47 -23
  84. package/src/components/PluginDetail.tsx +30 -6
  85. package/src/components/PluginManagerShell.tsx +24 -6
  86. package/src/components/PluginManagerTypes.ts +2 -0
  87. package/src/components/SessionSelector.tsx +33 -16
  88. package/src/components/StatusCommand.tsx +94 -0
  89. package/src/components/WorktreeExitPrompt.tsx +86 -0
  90. package/src/contexts/useChat.tsx +57 -13
  91. package/src/contracts/status.ts +7 -0
  92. package/src/hooks/useInputManager.ts +12 -0
  93. package/src/hooks/usePluginManager.ts +47 -13
  94. package/src/hooks/useTasks.ts +2 -2
  95. package/src/index.ts +71 -12
  96. package/src/managers/InputManager.ts +37 -15
  97. package/src/print-cli.ts +48 -5
  98. package/src/session-selector-cli.tsx +6 -2
  99. package/src/types.ts +11 -0
  100. package/src/utils/worktree.ts +164 -0
  101. package/dist/components/CommandOutputDisplay.d.ts +0 -9
  102. package/dist/components/CommandOutputDisplay.d.ts.map +0 -1
@@ -8,6 +8,7 @@ import { BackgroundTaskManager } from "./BackgroundTaskManager.js";
8
8
  import { McpManager } from "./McpManager.js";
9
9
  import { RewindCommand } from "./RewindCommand.js";
10
10
  import { HelpView } from "./HelpView.js";
11
+ import { StatusCommand } from "./StatusCommand.js";
11
12
  import { useInputManager } from "../hooks/useInputManager.js";
12
13
  import { useChat } from "../contexts/useChat.js";
13
14
 
@@ -57,6 +58,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
57
58
  backgroundCurrentTask,
58
59
  messages,
59
60
  getFullMessageThread,
61
+ clearMessages,
60
62
  } = useChat();
61
63
 
62
64
  // Input manager with all input state and functionality (including images)
@@ -88,10 +90,12 @@ export const InputBox: React.FC<InputBoxProps> = ({
88
90
  showMcpManager,
89
91
  showRewindManager,
90
92
  showHelp,
93
+ showStatusCommand,
91
94
  setShowBackgroundTaskManager,
92
95
  setShowMcpManager,
93
96
  setShowRewindManager,
94
97
  setShowHelp,
98
+ setShowStatusCommand,
95
99
  // Permission mode
96
100
  permissionMode,
97
101
  setPermissionMode,
@@ -105,6 +109,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
105
109
  onAbortMessage: abortMessage,
106
110
  onBackgroundCurrentTask: backgroundCurrentTask,
107
111
  onPermissionModeChange: setChatPermissionMode,
112
+ onClearMessages: clearMessages,
108
113
  });
109
114
 
110
115
  // Sync permission mode from useChat to InputManager
@@ -172,6 +177,10 @@ export const InputBox: React.FC<InputBoxProps> = ({
172
177
  return <HelpView onCancel={() => setShowHelp(false)} />;
173
178
  }
174
179
 
180
+ if (showStatusCommand) {
181
+ return <StatusCommand onCancel={() => setShowStatusCommand(false)} />;
182
+ }
183
+
175
184
  return (
176
185
  <Box flexDirection="column">
177
186
  {showFileSelector && (
@@ -219,7 +228,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
219
228
  {showBackgroundTaskManager ||
220
229
  showMcpManager ||
221
230
  showRewindManager ||
222
- showHelp || (
231
+ showHelp ||
232
+ showStatusCommand || (
223
233
  <Box flexDirection="column">
224
234
  <Box
225
235
  borderStyle="single"
@@ -3,17 +3,21 @@ import { Box, Text, useInput } from "ink";
3
3
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
4
4
 
5
5
  export const MarketplaceAddForm: React.FC = () => {
6
- const { actions } = usePluginManagerContext();
6
+ const { state, actions } = usePluginManagerContext();
7
7
  const [source, setSource] = useState("");
8
8
 
9
9
  useInput((input, key) => {
10
10
  if (key.escape) {
11
11
  actions.setView("MARKETPLACES");
12
+ } else if (state.isLoading) {
13
+ return;
12
14
  } else if (key.return) {
13
- if (source.trim()) {
14
- actions.addMarketplace(source.trim());
15
- actions.setView("MARKETPLACES");
16
- }
15
+ setSource((prev) => {
16
+ if (prev.trim()) {
17
+ actions.addMarketplace(prev.trim());
18
+ }
19
+ return prev;
20
+ });
17
21
  } else if (key.backspace || key.delete) {
18
22
  setSource((prev) => prev.slice(0, -1));
19
23
  } else if (input.length === 1) {
@@ -28,11 +32,20 @@ export const MarketplaceAddForm: React.FC = () => {
28
32
  </Text>
29
33
  <Box marginTop={1}>
30
34
  <Text>Source (URL or Path): </Text>
31
- <Text color="yellow">{source}</Text>
32
- <Text color="yellow">_</Text>
35
+ <Text color={state.isLoading ? "gray" : "yellow"}>{source}</Text>
36
+ {!state.isLoading && <Text color="yellow">_</Text>}
33
37
  </Box>
38
+ {state.isLoading && (
39
+ <Box marginTop={1}>
40
+ <Text color="yellow">⌛ Adding marketplace...</Text>
41
+ </Box>
42
+ )}
34
43
  <Box marginTop={1}>
35
- <Text dimColor>Press Enter to add, Esc to cancel</Text>
44
+ <Text dimColor>
45
+ {state.isLoading
46
+ ? "Please wait..."
47
+ : "Press Enter to add, Esc to cancel"}
48
+ </Text>
36
49
  </Box>
37
50
  </Box>
38
51
  );
@@ -24,14 +24,13 @@ export const MarketplaceDetail: React.FC = () => {
24
24
  setSelectedActionIndex((prev) =>
25
25
  prev < ACTIONS.length - 1 ? prev + 1 : 0,
26
26
  );
27
- } else if (key.return && marketplace) {
27
+ } else if (key.return && marketplace && !state.isLoading) {
28
28
  const action = ACTIONS[selectedActionIndex].id;
29
29
  if (action === "update") {
30
30
  actions.updateMarketplace(marketplace.name);
31
31
  } else {
32
32
  actions.removeMarketplace(marketplace.name);
33
33
  }
34
- actions.setView("MARKETPLACES");
35
34
  }
36
35
  });
37
36
 
@@ -56,19 +55,35 @@ export const MarketplaceDetail: React.FC = () => {
56
55
  <Text>Source: {JSON.stringify(marketplace.source)}</Text>
57
56
  </Box>
58
57
 
58
+ {state.isLoading && (
59
+ <Box marginBottom={1}>
60
+ <Text color="yellow">⌛ Processing operation...</Text>
61
+ </Box>
62
+ )}
63
+
59
64
  <Box marginTop={1} flexDirection="column">
60
65
  <Text bold>Marketplace Actions:</Text>
61
66
  {ACTIONS.map((action, index) => (
62
67
  <Text
63
68
  key={action.id}
64
- color={index === selectedActionIndex ? "yellow" : undefined}
69
+ color={
70
+ index === selectedActionIndex
71
+ ? state.isLoading
72
+ ? "gray"
73
+ : "yellow"
74
+ : undefined
75
+ }
65
76
  >
66
77
  {index === selectedActionIndex ? "> " : " "}
67
78
  {action.label}
68
79
  </Text>
69
80
  ))}
70
81
  <Box marginTop={1}>
71
- <Text dimColor>Use ↑/↓ to select, Enter to confirm</Text>
82
+ <Text dimColor>
83
+ {state.isLoading
84
+ ? "Please wait..."
85
+ : "Use ↑/↓ to select, Enter to confirm"}
86
+ </Text>
72
87
  </Box>
73
88
  <Box marginTop={1}>
74
89
  <Text dimColor>Press Esc to go back</Text>
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import type { Message, MessageBlock } from "wave-agent-sdk";
4
4
  import { MessageSource } from "wave-agent-sdk";
5
- import { CommandOutputDisplay } from "./CommandOutputDisplay.js";
5
+ import { BangDisplay } from "./BangDisplay.js";
6
6
  import { ToolDisplay } from "./ToolDisplay.js";
7
7
  import { CompressDisplay } from "./CompressDisplay.js";
8
8
  import { ReasoningDisplay } from "./ReasoningDisplay.js";
@@ -51,8 +51,8 @@ export const MessageBlockItem = ({
51
51
  </Box>
52
52
  )}
53
53
 
54
- {block.type === "command_output" && (
55
- <CommandOutputDisplay block={block} isExpanded={isExpanded} />
54
+ {block.type === "bang" && (
55
+ <BangDisplay block={block} isExpanded={isExpanded} />
56
56
  )}
57
57
 
58
58
  {block.type === "tool" && (
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import os from "os";
2
3
  import { Box, Text, Static } from "ink";
3
4
  import type { Message } from "wave-agent-sdk";
4
5
  import { MessageBlockItem } from "./MessageBlockItem.js";
@@ -7,6 +8,9 @@ export interface MessageListProps {
7
8
  messages: Message[];
8
9
  isExpanded?: boolean;
9
10
  hideDynamicBlocks?: boolean;
11
+ version?: string;
12
+ workdir?: string;
13
+ model?: string;
10
14
  }
11
15
 
12
16
  export const MessageList = React.memo(
@@ -14,17 +18,23 @@ export const MessageList = React.memo(
14
18
  messages,
15
19
  isExpanded = false,
16
20
  hideDynamicBlocks = false,
21
+ version,
22
+ workdir,
23
+ model,
17
24
  }: MessageListProps) => {
18
- // Empty message state
19
- if (messages.length === 0) {
20
- return (
21
- <Box flexDirection="column" gap={1}>
22
- <Box flexDirection="column" paddingY={1}>
23
- <Text color="gray">Welcome to WAVE Code Assistant!</Text>
24
- </Box>
25
- </Box>
26
- );
27
- }
25
+ const welcomeMessage = (
26
+ <Box flexDirection="column" paddingTop={1}>
27
+ <Text color="gray">
28
+ WAVE{version ? ` v${version}` : ""}
29
+ {model ? ` • ${model}` : ""}
30
+ </Text>
31
+ {workdir && (
32
+ <Text color="gray" wrap="truncate-middle">
33
+ {workdir.replace(os.homedir(), "~")}
34
+ </Text>
35
+ )}
36
+ </Box>
37
+ );
28
38
 
29
39
  // Limit messages when expanded to prevent long rendering times
30
40
  const maxExpandedMessages = 20;
@@ -54,7 +64,7 @@ export const MessageList = React.memo(
54
64
  const isDynamic =
55
65
  isLastMessage &&
56
66
  ((block.type === "tool" && block.stage !== "end") ||
57
- (block.type === "command_output" && block.isRunning));
67
+ (block.type === "bang" && block.isRunning));
58
68
  return { ...item, isDynamic };
59
69
  });
60
70
 
@@ -63,20 +73,34 @@ export const MessageList = React.memo(
63
73
  ? []
64
74
  : blocksWithStatus.filter((b) => b.isDynamic);
65
75
 
76
+ const staticItems = [
77
+ { isWelcome: true, key: "welcome", block: undefined, message: undefined },
78
+ ...staticBlocks.map((b) => ({ ...b, isWelcome: false })),
79
+ ];
80
+
66
81
  return (
67
82
  <Box flexDirection="column" paddingBottom={1}>
68
- {/* Static blocks */}
69
- {staticBlocks.length > 0 && (
70
- <Static items={staticBlocks}>
71
- {(item) => (
72
- <MessageBlockItem
73
- key={item.key}
74
- block={item.block}
75
- message={item.message}
76
- isExpanded={isExpanded}
77
- paddingTop={1}
78
- />
79
- )}
83
+ {/* Static items (Welcome message + Static blocks) */}
84
+ {staticItems.length > 0 && (
85
+ <Static items={staticItems}>
86
+ {(item) => {
87
+ if (item.isWelcome) {
88
+ return (
89
+ <React.Fragment key={item.key}>
90
+ {welcomeMessage}
91
+ </React.Fragment>
92
+ );
93
+ }
94
+ return (
95
+ <MessageBlockItem
96
+ key={item.key}
97
+ block={item.block!}
98
+ message={item.message!}
99
+ isExpanded={isExpanded}
100
+ paddingTop={1}
101
+ />
102
+ );
103
+ }}
80
104
  </Static>
81
105
  )}
82
106
 
@@ -49,7 +49,7 @@ export const PluginDetail: React.FC = () => {
49
49
  setSelectedScopeIndex((prev) =>
50
50
  prev < SCOPES.length - 1 ? prev + 1 : 0,
51
51
  );
52
- } else if (key.return && plugin) {
52
+ } else if (key.return && plugin && !state.isLoading) {
53
53
  if (isInstalledAndEnabled) {
54
54
  setSelectedActionIndex((prev) => {
55
55
  const action = INSTALLED_ACTIONS[prev].id;
@@ -70,7 +70,6 @@ export const PluginDetail: React.FC = () => {
70
70
  return prev;
71
71
  });
72
72
  }
73
- actions.setView("INSTALLED");
74
73
  }
75
74
  });
76
75
 
@@ -102,6 +101,11 @@ export const PluginDetail: React.FC = () => {
102
101
  </Text>
103
102
  </Box>
104
103
  )}
104
+ {state.isLoading && (
105
+ <Box marginBottom={1}>
106
+ <Text color="yellow">⌛ Processing operation...</Text>
107
+ </Box>
108
+ )}
105
109
 
106
110
  <Box marginTop={1} flexDirection="column">
107
111
  {isInstalledAndEnabled ? (
@@ -110,14 +114,24 @@ export const PluginDetail: React.FC = () => {
110
114
  {INSTALLED_ACTIONS.map((action, index) => (
111
115
  <Text
112
116
  key={action.id}
113
- color={index === selectedActionIndex ? "yellow" : undefined}
117
+ color={
118
+ index === selectedActionIndex
119
+ ? state.isLoading
120
+ ? "gray"
121
+ : "yellow"
122
+ : undefined
123
+ }
114
124
  >
115
125
  {index === selectedActionIndex ? "> " : " "}
116
126
  {action.label}
117
127
  </Text>
118
128
  ))}
119
129
  <Box marginTop={1}>
120
- <Text dimColor>Use ↑/↓ to select, Enter to confirm</Text>
130
+ <Text dimColor>
131
+ {state.isLoading
132
+ ? "Please wait..."
133
+ : "Use ↑/↓ to select, Enter to confirm"}
134
+ </Text>
121
135
  </Box>
122
136
  </Box>
123
137
  ) : (
@@ -126,14 +140,24 @@ export const PluginDetail: React.FC = () => {
126
140
  {SCOPES.map((scope, index) => (
127
141
  <Text
128
142
  key={scope.id}
129
- color={index === selectedScopeIndex ? "green" : undefined}
143
+ color={
144
+ index === selectedScopeIndex
145
+ ? state.isLoading
146
+ ? "gray"
147
+ : "green"
148
+ : undefined
149
+ }
130
150
  >
131
151
  {index === selectedScopeIndex ? "> " : " "}
132
152
  {scope.label}
133
153
  </Text>
134
154
  ))}
135
155
  <Box marginTop={1}>
136
- <Text dimColor>Use ↑/↓ to select, Enter to install</Text>
156
+ <Text dimColor>
157
+ {state.isLoading
158
+ ? "Please wait..."
159
+ : "Use ↑/↓ to select, Enter to install"}
160
+ </Text>
137
161
  </Box>
138
162
  </Box>
139
163
  )}
@@ -172,14 +172,32 @@ export const PluginManagerShell: React.FC<{ children?: React.ReactNode }> = ({
172
172
  borderLeft={false}
173
173
  borderRight={false}
174
174
  >
175
- <Text dimColor>
176
- {state.isLoading
177
- ? "Loading..."
178
- : "Use Tab to switch views, arrows to navigate, Enter to select, Esc to go back"}
179
- </Text>
175
+ <Box flexGrow={1}>
176
+ <Text dimColor>
177
+ {state.isLoading
178
+ ? "Loading..."
179
+ : "Use Tab to switch views, arrows to navigate, Enter to select, Esc to go back"}
180
+ </Text>
181
+ </Box>
182
+ {state.isLoading && (
183
+ <Box marginLeft={2}>
184
+ <Text color="yellow" bold>
185
+ ⌛ Processing...
186
+ </Text>
187
+ </Box>
188
+ )}
189
+ {state.successMessage && (
190
+ <Box marginLeft={2}>
191
+ <Text color="green" bold>
192
+ ✓ {state.successMessage}
193
+ </Text>
194
+ </Box>
195
+ )}
180
196
  {state.error && (
181
197
  <Box marginLeft={2}>
182
- <Text color="red">Error: {state.error}</Text>
198
+ <Text color="red" bold>
199
+ ✗ Error: {state.error}
200
+ </Text>
183
201
  </Box>
184
202
  )}
185
203
  </Box>
@@ -17,6 +17,7 @@ export interface PluginManagerState {
17
17
  selectedId: string | null; // Plugin name or Marketplace name
18
18
  isLoading: boolean;
19
19
  error: string | null;
20
+ successMessage: string | null;
20
21
  searchQuery: string;
21
22
  }
22
23
 
@@ -43,5 +44,6 @@ export interface PluginManagerContextType {
43
44
  uninstallPlugin: (name: string, marketplace: string) => Promise<void>;
44
45
  updatePlugin: (name: string, marketplace: string) => Promise<void>;
45
46
  refresh: () => Promise<void>;
47
+ clearPluginFeedback: () => void;
46
48
  };
47
49
  }
@@ -56,15 +56,18 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
56
56
  );
57
57
  }
58
58
 
59
- const maxDisplay = 10;
59
+ const MAX_VISIBLE_ITEMS = 3;
60
60
  const startIndex = Math.max(
61
61
  0,
62
62
  Math.min(
63
- selectedIndex - Math.floor(maxDisplay / 2),
64
- sessions.length - maxDisplay,
63
+ selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
64
+ Math.max(0, sessions.length - MAX_VISIBLE_ITEMS),
65
65
  ),
66
66
  );
67
- const displaySessions = sessions.slice(startIndex, startIndex + maxDisplay);
67
+ const displaySessions = sessions.slice(
68
+ startIndex,
69
+ startIndex + MAX_VISIBLE_ITEMS,
70
+ );
68
71
 
69
72
  return (
70
73
  <Box
@@ -92,26 +95,40 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
92
95
  return (
93
96
  <Box key={session.id} flexDirection="column" width="100%">
94
97
  <Box width="100%">
95
- <Text
96
- color={isSelected ? "black" : "white"}
98
+ <Box
97
99
  backgroundColor={isSelected ? "cyan" : undefined}
100
+ flexShrink={0}
98
101
  >
99
- {isSelected ? "" : " "}
100
- {session.id} | {lastActiveAt} | {session.latestTotalTokens}{" "}
101
- tokens
102
- </Text>
103
- </Box>
104
- <Box marginLeft={4} width="100%">
105
- <Text dimColor italic>
106
- {session.firstMessage}
107
- </Text>
102
+ <Text color={isSelected ? "black" : "white"}>
103
+ {isSelected ? "▶ " : " "}
104
+ </Text>
105
+ </Box>
106
+ <Box
107
+ backgroundColor={isSelected ? "cyan" : undefined}
108
+ flexGrow={1}
109
+ >
110
+ <Text
111
+ color={isSelected ? "black" : "white"}
112
+ wrap="truncate-end"
113
+ >
114
+ {session.id} | {lastActiveAt} | {session.latestTotalTokens}{" "}
115
+ tokens
116
+ </Text>
117
+ </Box>
108
118
  </Box>
119
+ {isSelected && session.firstMessage && (
120
+ <Box marginLeft={2} width="100%">
121
+ <Text dimColor italic wrap="truncate-end">
122
+ {session.firstMessage.replace(/\n/g, "\\n")}
123
+ </Text>
124
+ </Box>
125
+ )}
109
126
  </Box>
110
127
  );
111
128
  })}
112
129
  </Box>
113
130
 
114
- {sessions.length > maxDisplay && (
131
+ {sessions.length > MAX_VISIBLE_ITEMS && (
115
132
  <Box>
116
133
  <Text dimColor>
117
134
  ... showing {displaySessions.length} of {sessions.length} sessions
@@ -0,0 +1,94 @@
1
+ import React from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { useChat } from "../contexts/useChat.js";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ export interface StatusCommandProps {
12
+ onCancel: () => void;
13
+ }
14
+
15
+ export const StatusCommand: React.FC<StatusCommandProps> = ({ onCancel }) => {
16
+ const { sessionId, workingDirectory, getGatewayConfig, getModelConfig } =
17
+ useChat();
18
+
19
+ useInput((_, key) => {
20
+ if (key.escape) {
21
+ onCancel();
22
+ }
23
+ });
24
+
25
+ const gatewayConfig = getGatewayConfig();
26
+ const modelConfig = getModelConfig();
27
+
28
+ let version = "unknown";
29
+ try {
30
+ const pkgPath = path.resolve(__dirname, "../../package.json");
31
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
32
+ version = pkg.version;
33
+ } catch {
34
+ // Fallback if package.json cannot be read
35
+ }
36
+
37
+ return (
38
+ <Box
39
+ flexDirection="column"
40
+ borderStyle="round"
41
+ borderColor="cyan"
42
+ borderLeft={false}
43
+ borderRight={false}
44
+ paddingX={1}
45
+ >
46
+ <Box marginBottom={1}>
47
+ <Text color="cyan" bold underline>
48
+ Agent Status
49
+ </Text>
50
+ </Box>
51
+
52
+ <Box>
53
+ <Box width={20}>
54
+ <Text color="yellow">Version:</Text>
55
+ </Box>
56
+ <Text color="white">{version}</Text>
57
+ </Box>
58
+
59
+ <Box>
60
+ <Box width={20}>
61
+ <Text color="yellow">Session ID:</Text>
62
+ </Box>
63
+ <Text color="white">{sessionId}</Text>
64
+ </Box>
65
+
66
+ <Box>
67
+ <Box width={20}>
68
+ <Text color="yellow">cwd:</Text>
69
+ </Box>
70
+ <Text color="white" wrap="wrap">
71
+ {workingDirectory}
72
+ </Text>
73
+ </Box>
74
+
75
+ <Box>
76
+ <Box width={20}>
77
+ <Text color="yellow">Wave base URL:</Text>
78
+ </Box>
79
+ <Text color="white">{gatewayConfig.baseURL}</Text>
80
+ </Box>
81
+
82
+ <Box>
83
+ <Box width={20}>
84
+ <Text color="yellow">Model:</Text>
85
+ </Box>
86
+ <Text color="white">{modelConfig.model}</Text>
87
+ </Box>
88
+
89
+ <Box marginTop={1}>
90
+ <Text dimColor>Esc to cancel</Text>
91
+ </Box>
92
+ </Box>
93
+ );
94
+ };
@@ -0,0 +1,86 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+
4
+ interface WorktreeExitPromptProps {
5
+ name: string;
6
+ path: string;
7
+ hasUncommittedChanges: boolean;
8
+ hasNewCommits: boolean;
9
+ onKeep: () => void;
10
+ onRemove: () => void;
11
+ onCancel: () => void;
12
+ }
13
+
14
+ export const WorktreeExitPrompt: React.FC<WorktreeExitPromptProps> = ({
15
+ name,
16
+ path: worktreePath,
17
+ hasUncommittedChanges,
18
+ hasNewCommits,
19
+ onKeep,
20
+ onRemove,
21
+ onCancel,
22
+ }) => {
23
+ const [selectedIndex, setSelectedIndex] = useState(0);
24
+
25
+ useInput((input, key) => {
26
+ if (key.upArrow) {
27
+ setSelectedIndex((prev) => (prev === 0 ? 1 : 0));
28
+ }
29
+ if (key.downArrow) {
30
+ setSelectedIndex((prev) => (prev === 1 ? 0 : 1));
31
+ }
32
+ if (key.return) {
33
+ if (selectedIndex === 0) {
34
+ onKeep();
35
+ } else {
36
+ onRemove();
37
+ }
38
+ }
39
+ if (key.escape) {
40
+ onCancel();
41
+ }
42
+ });
43
+
44
+ return (
45
+ <Box flexDirection="column" paddingX={1} marginY={1}>
46
+ <Box marginBottom={1}>
47
+ <Text bold>Exiting worktree session</Text>
48
+ </Box>
49
+ <Box marginBottom={1}>
50
+ {hasUncommittedChanges && hasNewCommits ? (
51
+ <Text>
52
+ You have uncommitted changes and new commits. These will be lost if
53
+ you remove the worktree.
54
+ </Text>
55
+ ) : hasUncommittedChanges ? (
56
+ <Text>
57
+ You have uncommitted changes. These will be lost if you remove the
58
+ worktree.
59
+ </Text>
60
+ ) : (
61
+ <Text>
62
+ You have new commits on worktree-{name}. The branch will be deleted
63
+ if you remove the worktree.
64
+ </Text>
65
+ )}
66
+ </Box>
67
+ <Box flexDirection="column">
68
+ <Box>
69
+ <Text color={selectedIndex === 0 ? "cyan" : undefined}>
70
+ {selectedIndex === 0 ? "❯ " : " "}Keep worktree Stays at{" "}
71
+ {worktreePath}
72
+ </Text>
73
+ </Box>
74
+ <Box>
75
+ <Text color={selectedIndex === 1 ? "cyan" : undefined}>
76
+ {selectedIndex === 1 ? "❯ " : " "}Remove worktree All changes and
77
+ commits will be lost.
78
+ </Text>
79
+ </Box>
80
+ </Box>
81
+ <Box marginTop={1}>
82
+ <Text dimColor>Enter to confirm · Esc to cancel</Text>
83
+ </Box>
84
+ </Box>
85
+ );
86
+ };