@ebowwa/coder 0.7.64 → 0.7.66
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.
- package/dist/index.js +36233 -32
- package/dist/interfaces/ui/terminal/cli/index.js +34318 -158
- package/dist/interfaces/ui/terminal/native/README.md +53 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
- package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
- package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
- package/dist/interfaces/ui/terminal/native/index.js +43 -0
- package/dist/interfaces/ui/terminal/native/index.node +0 -0
- package/dist/interfaces/ui/terminal/native/package.json +34 -0
- package/dist/native/README.md +53 -0
- package/dist/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/native/claude_code_native.dylib +0 -0
- package/dist/native/index.d.ts +0 -480
- package/dist/native/index.darwin-arm64.node +0 -0
- package/dist/native/index.js +43 -1625
- package/dist/native/index.node +0 -0
- package/dist/native/package.json +34 -0
- package/native/index.darwin-arm64.node +0 -0
- package/native/index.js +33 -19
- package/package.json +3 -2
- package/packages/src/core/agent-loop/__tests__/compaction.test.ts +17 -14
- package/packages/src/core/agent-loop/compaction.ts +6 -2
- package/packages/src/core/agent-loop/index.ts +2 -0
- package/packages/src/core/agent-loop/loop-state.ts +1 -1
- package/packages/src/core/agent-loop/turn-executor.ts +4 -0
- package/packages/src/core/agent-loop/types.ts +4 -0
- package/packages/src/core/api-client-impl.ts +377 -176
- package/packages/src/core/cognitive-security/hooks.ts +2 -1
- package/packages/src/core/config/todo +7 -0
- package/packages/src/core/context/__tests__/integration.test.ts +334 -0
- package/packages/src/core/context/compaction.ts +170 -0
- package/packages/src/core/context/constants.ts +58 -0
- package/packages/src/core/context/extraction.ts +85 -0
- package/packages/src/core/context/index.ts +66 -0
- package/packages/src/core/context/summarization.ts +251 -0
- package/packages/src/core/context/token-estimation.ts +98 -0
- package/packages/src/core/context/types.ts +59 -0
- package/packages/src/core/models.ts +81 -4
- package/packages/src/core/normalizers/todo +5 -1
- package/packages/src/core/providers/README.md +230 -0
- package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
- package/packages/src/core/providers/index.ts +419 -0
- package/packages/src/core/providers/types.ts +132 -0
- package/packages/src/core/retry.ts +10 -0
- package/packages/src/ecosystem/tools/index.ts +174 -0
- package/packages/src/index.ts +23 -2
- package/packages/src/interfaces/ui/index.ts +17 -20
- package/packages/src/interfaces/ui/spinner.ts +2 -2
- package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
- package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
- package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
- package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
- package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
- package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
- package/packages/src/interfaces/ui/terminal/cli/index.ts +200 -13
- package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +402 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
- package/packages/src/interfaces/ui/terminal/shared/index.ts +13 -0
- package/packages/src/interfaces/ui/terminal/shared/query.ts +9 -3
- package/packages/src/interfaces/ui/terminal/shared/setup.ts +5 -1
- package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
- package/packages/src/interfaces/ui/terminal/shared/status-line.ts +10 -2
- package/packages/src/native/index.ts +404 -27
- package/packages/src/native/tui_v2_types.ts +39 -0
- package/packages/src/teammates/coordination.test.ts +279 -0
- package/packages/src/teammates/coordination.ts +646 -0
- package/packages/src/teammates/index.ts +95 -25
- package/packages/src/teammates/integration.test.ts +272 -0
- package/packages/src/teammates/runner.test.ts +235 -0
- package/packages/src/teammates/runner.ts +750 -0
- package/packages/src/teammates/schemas.ts +673 -0
- package/packages/src/types/index.ts +1 -0
- package/packages/src/core/context-compaction.ts +0 -578
- package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
- package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
- package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +0 -262
- package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +0 -232
- package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +0 -62
- package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +0 -537
- package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +0 -107
- package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +0 -240
- package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +0 -54
- package/packages/src/interfaces/ui/terminal/tui/commands.ts +0 -438
- package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +0 -584
- package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +0 -614
- package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +0 -333
- package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +0 -604
- package/packages/src/interfaces/ui/terminal/tui/components/index.ts +0 -118
- package/packages/src/interfaces/ui/terminal/tui/console.ts +0 -49
- package/packages/src/interfaces/ui/terminal/tui/index.ts +0 -90
- package/packages/src/interfaces/ui/terminal/tui/run.tsx +0 -42
- package/packages/src/interfaces/ui/terminal/tui/spinner.ts +0 -69
- package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +0 -390
- package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +0 -422
- package/packages/src/interfaces/ui/terminal/tui/types.ts +0 -186
- package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +0 -104
- package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +0 -239
|
@@ -1,604 +0,0 @@
|
|
|
1
|
-
/** @jsx React.createElement */
|
|
2
|
-
/**
|
|
3
|
-
* Sidebar Component
|
|
4
|
-
* Collapsible sidebar for sessions, files, todos, and tools
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Collapsible with keyboard shortcut
|
|
8
|
-
* - Tabbed sections (sessions, files, todos, tools)
|
|
9
|
-
* - Keyboard navigation
|
|
10
|
-
* - Context-aware content
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
14
|
-
import { Box, Text, useStdout } from "ink";
|
|
15
|
-
import chalk from "chalk";
|
|
16
|
-
|
|
17
|
-
// ============================================
|
|
18
|
-
// TYPES
|
|
19
|
-
// ============================================
|
|
20
|
-
|
|
21
|
-
export type SidebarTab = "sessions" | "files" | "todos" | "tools";
|
|
22
|
-
|
|
23
|
-
export interface SidebarSession {
|
|
24
|
-
id: string;
|
|
25
|
-
messageCount: number;
|
|
26
|
-
lastActivity?: number;
|
|
27
|
-
model?: string;
|
|
28
|
-
preview?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface SidebarFile {
|
|
32
|
-
path: string;
|
|
33
|
-
status: "modified" | "added" | "deleted" | "untracked";
|
|
34
|
-
lineCount?: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SidebarTodo {
|
|
38
|
-
id: string;
|
|
39
|
-
text: string;
|
|
40
|
-
status: "pending" | "in_progress" | "completed";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface SidebarTool {
|
|
44
|
-
name: string;
|
|
45
|
-
server: string;
|
|
46
|
-
status: "available" | "running" | "error";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface SidebarProps {
|
|
50
|
-
/** Whether sidebar is visible */
|
|
51
|
-
isOpen: boolean;
|
|
52
|
-
/** Current active tab */
|
|
53
|
-
activeTab?: SidebarTab;
|
|
54
|
-
/** Width in columns */
|
|
55
|
-
width?: number;
|
|
56
|
-
/** Sessions list */
|
|
57
|
-
sessions?: SidebarSession[];
|
|
58
|
-
/** Modified files list */
|
|
59
|
-
files?: SidebarFile[];
|
|
60
|
-
/** Todos list */
|
|
61
|
-
todos?: SidebarTodo[];
|
|
62
|
-
/** Available tools list */
|
|
63
|
-
tools?: SidebarTool[];
|
|
64
|
-
/** Current session ID */
|
|
65
|
-
currentSessionId?: string;
|
|
66
|
-
/** Called when tab changes */
|
|
67
|
-
onTabChange?: (tab: SidebarTab) => void;
|
|
68
|
-
/** Called when session selected */
|
|
69
|
-
onSessionSelect?: (sessionId: string) => void;
|
|
70
|
-
/** Called when file selected */
|
|
71
|
-
onFileSelect?: (path: string) => void;
|
|
72
|
-
/** Called when todo toggled */
|
|
73
|
-
onTodoToggle?: (todoId: string) => void;
|
|
74
|
-
/** Called when tool selected */
|
|
75
|
-
onToolSelect?: (toolName: string) => void;
|
|
76
|
-
/** Called when close requested */
|
|
77
|
-
onClose?: () => void;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ============================================
|
|
81
|
-
// TAB CONFIGURATION
|
|
82
|
-
// ============================================
|
|
83
|
-
|
|
84
|
-
const TABS: { id: SidebarTab; label: string; icon: string }[] = [
|
|
85
|
-
{ id: "sessions", label: "Sessions", icon: "◈" },
|
|
86
|
-
{ id: "files", label: "Files", icon: "◇" },
|
|
87
|
-
{ id: "todos", label: "Todos", icon: "○" },
|
|
88
|
-
{ id: "tools", label: "Tools", icon: "◆" },
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
// ============================================
|
|
92
|
-
// SUB-COMPONENTS
|
|
93
|
-
// ============================================
|
|
94
|
-
|
|
95
|
-
interface TabBarProps {
|
|
96
|
-
activeTab: SidebarTab;
|
|
97
|
-
onTabChange: (tab: SidebarTab) => void;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function TabBar({ activeTab, onTabChange }: TabBarProps) {
|
|
101
|
-
return (
|
|
102
|
-
<Box flexDirection="row" borderBottom>
|
|
103
|
-
{TABS.map((tab) => {
|
|
104
|
-
const isActive = tab.id === activeTab;
|
|
105
|
-
return (
|
|
106
|
-
<Box
|
|
107
|
-
key={tab.id}
|
|
108
|
-
paddingX={1}
|
|
109
|
-
borderStyle={isActive ? "single" : undefined}
|
|
110
|
-
borderBottom={isActive}
|
|
111
|
-
>
|
|
112
|
-
<Text
|
|
113
|
-
color={isActive ? "cyan" : "gray"}
|
|
114
|
-
bold={isActive}
|
|
115
|
-
>
|
|
116
|
-
{tab.icon} {tab.label}
|
|
117
|
-
</Text>
|
|
118
|
-
</Box>
|
|
119
|
-
);
|
|
120
|
-
})}
|
|
121
|
-
</Box>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ============================================
|
|
126
|
-
// SESSIONS TAB
|
|
127
|
-
// ============================================
|
|
128
|
-
|
|
129
|
-
interface SessionsTabProps {
|
|
130
|
-
sessions: SidebarSession[];
|
|
131
|
-
currentSessionId?: string;
|
|
132
|
-
onSelect?: (sessionId: string) => void;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function SessionsTab({ sessions, currentSessionId, onSelect }: SessionsTabProps) {
|
|
136
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
137
|
-
|
|
138
|
-
useEffect(() => {
|
|
139
|
-
const handleKey = (data: Buffer) => {
|
|
140
|
-
const key = data.toString();
|
|
141
|
-
|
|
142
|
-
if (key === "\x1b[A" || key === "k") {
|
|
143
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
144
|
-
} else if (key === "\x1b[B" || key === "j") {
|
|
145
|
-
setSelectedIndex((prev) => Math.min(sessions.length - 1, prev + 1));
|
|
146
|
-
} else if (key === "\r" || key === "\n" || key === "l") {
|
|
147
|
-
const session = sessions[selectedIndex];
|
|
148
|
-
if (session) {
|
|
149
|
-
onSelect?.(session.id);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
process.stdin.on("data", handleKey);
|
|
155
|
-
return () => {
|
|
156
|
-
process.stdin.off("data", handleKey);
|
|
157
|
-
};
|
|
158
|
-
}, [sessions, selectedIndex, onSelect]);
|
|
159
|
-
|
|
160
|
-
if (sessions.length === 0) {
|
|
161
|
-
return (
|
|
162
|
-
<Box paddingX={1}>
|
|
163
|
-
<Text dimColor>No recent sessions</Text>
|
|
164
|
-
</Box>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<Box flexDirection="column">
|
|
170
|
-
{sessions.slice(0, 15).map((session, index) => {
|
|
171
|
-
const isSelected = index === selectedIndex;
|
|
172
|
-
const isCurrent = session.id === currentSessionId;
|
|
173
|
-
|
|
174
|
-
const timeAgo = session.lastActivity
|
|
175
|
-
? formatTimeAgo(session.lastActivity)
|
|
176
|
-
: "unknown";
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<Box
|
|
180
|
-
key={session.id}
|
|
181
|
-
paddingX={1}
|
|
182
|
-
flexDirection="column"
|
|
183
|
-
>
|
|
184
|
-
<Box>
|
|
185
|
-
{isSelected && <Text color="cyan">→ </Text>}
|
|
186
|
-
{!isSelected && <Text> </Text>}
|
|
187
|
-
{isCurrent && <Text color="green">● </Text>}
|
|
188
|
-
{!isCurrent && <Text dimColor>○ </Text>}
|
|
189
|
-
<Text
|
|
190
|
-
color={isSelected ? "cyan" : "white"}
|
|
191
|
-
bold={isSelected}
|
|
192
|
-
>
|
|
193
|
-
{session.id.slice(0, 8)}
|
|
194
|
-
</Text>
|
|
195
|
-
<Text dimColor> {session.model?.slice(0, 10) || "unknown"}</Text>
|
|
196
|
-
</Box>
|
|
197
|
-
<Box marginLeft={3}>
|
|
198
|
-
<Text dimColor>
|
|
199
|
-
{session.messageCount} msgs · {timeAgo}
|
|
200
|
-
</Text>
|
|
201
|
-
</Box>
|
|
202
|
-
{session.preview && (
|
|
203
|
-
<Box marginLeft={3}>
|
|
204
|
-
<Text dimColor>
|
|
205
|
-
{session.preview.slice(0, 30)}
|
|
206
|
-
</Text>
|
|
207
|
-
</Box>
|
|
208
|
-
)}
|
|
209
|
-
</Box>
|
|
210
|
-
);
|
|
211
|
-
})}
|
|
212
|
-
</Box>
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ============================================
|
|
217
|
-
// FILES TAB
|
|
218
|
-
// ============================================
|
|
219
|
-
|
|
220
|
-
const FILE_STATUS_COLORS: Record<string, string> = {
|
|
221
|
-
modified: "yellow",
|
|
222
|
-
added: "green",
|
|
223
|
-
deleted: "red",
|
|
224
|
-
untracked: "gray",
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const FILE_STATUS_ICONS: Record<string, string> = {
|
|
228
|
-
modified: "M",
|
|
229
|
-
added: "A",
|
|
230
|
-
deleted: "D",
|
|
231
|
-
untracked: "?",
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
interface FilesTabProps {
|
|
235
|
-
files: SidebarFile[];
|
|
236
|
-
onSelect?: (path: string) => void;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function FilesTab({ files, onSelect }: FilesTabProps) {
|
|
240
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
241
|
-
|
|
242
|
-
useEffect(() => {
|
|
243
|
-
const handleKey = (data: Buffer) => {
|
|
244
|
-
const key = data.toString();
|
|
245
|
-
|
|
246
|
-
if (key === "\x1b[A" || key === "k") {
|
|
247
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
248
|
-
} else if (key === "\x1b[B" || key === "j") {
|
|
249
|
-
setSelectedIndex((prev) => Math.min(files.length - 1, prev + 1));
|
|
250
|
-
} else if (key === "\r" || key === "\n" || key === "l") {
|
|
251
|
-
const file = files[selectedIndex];
|
|
252
|
-
if (file) {
|
|
253
|
-
onSelect?.(file.path);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
process.stdin.on("data", handleKey);
|
|
259
|
-
return () => {
|
|
260
|
-
process.stdin.off("data", handleKey);
|
|
261
|
-
};
|
|
262
|
-
}, [files, selectedIndex, onSelect]);
|
|
263
|
-
|
|
264
|
-
if (files.length === 0) {
|
|
265
|
-
return (
|
|
266
|
-
<Box paddingX={1}>
|
|
267
|
-
<Text dimColor>No modified files</Text>
|
|
268
|
-
</Box>
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<Box flexDirection="column">
|
|
274
|
-
{files.slice(0, 15).map((file, index) => {
|
|
275
|
-
const isSelected = index === selectedIndex;
|
|
276
|
-
const color = FILE_STATUS_COLORS[file.status];
|
|
277
|
-
const icon = FILE_STATUS_ICONS[file.status];
|
|
278
|
-
|
|
279
|
-
return (
|
|
280
|
-
<Box key={file.path} paddingX={1}>
|
|
281
|
-
{isSelected && <Text color="cyan">→ </Text>}
|
|
282
|
-
{!isSelected && <Text> </Text>}
|
|
283
|
-
<Text color={color} bold>{icon} </Text>
|
|
284
|
-
<Text color={isSelected ? "cyan" : "white"}>
|
|
285
|
-
{truncatePath(file.path, 25)}
|
|
286
|
-
</Text>
|
|
287
|
-
{file.lineCount && (
|
|
288
|
-
<Text dimColor> ({file.lineCount} lines)</Text>
|
|
289
|
-
)}
|
|
290
|
-
</Box>
|
|
291
|
-
);
|
|
292
|
-
})}
|
|
293
|
-
</Box>
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// ============================================
|
|
298
|
-
// TODOS TAB
|
|
299
|
-
// ============================================
|
|
300
|
-
|
|
301
|
-
const TODO_STATUS_COLORS: Record<string, string> = {
|
|
302
|
-
pending: "gray",
|
|
303
|
-
in_progress: "yellow",
|
|
304
|
-
completed: "green",
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const TODO_STATUS_ICONS: Record<string, string> = {
|
|
308
|
-
pending: "○",
|
|
309
|
-
in_progress: "◐",
|
|
310
|
-
completed: "●",
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
interface TodosTabProps {
|
|
314
|
-
todos: SidebarTodo[];
|
|
315
|
-
onToggle?: (todoId: string) => void;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function TodosTab({ todos, onToggle }: TodosTabProps) {
|
|
319
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
320
|
-
|
|
321
|
-
useEffect(() => {
|
|
322
|
-
const handleKey = (data: Buffer) => {
|
|
323
|
-
const key = data.toString();
|
|
324
|
-
|
|
325
|
-
if (key === "\x1b[A" || key === "k") {
|
|
326
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
327
|
-
} else if (key === "\x1b[B" || key === "j") {
|
|
328
|
-
setSelectedIndex((prev) => Math.min(todos.length - 1, prev + 1));
|
|
329
|
-
} else if (key === "\r" || key === "\n" || key === " " || key === "l") {
|
|
330
|
-
const todo = todos[selectedIndex];
|
|
331
|
-
if (todo) {
|
|
332
|
-
onToggle?.(todo.id);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
process.stdin.on("data", handleKey);
|
|
338
|
-
return () => {
|
|
339
|
-
process.stdin.off("data", handleKey);
|
|
340
|
-
};
|
|
341
|
-
}, [todos, selectedIndex, onToggle]);
|
|
342
|
-
|
|
343
|
-
if (todos.length === 0) {
|
|
344
|
-
return (
|
|
345
|
-
<Box paddingX={1}>
|
|
346
|
-
<Text dimColor>No active todos</Text>
|
|
347
|
-
</Box>
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const pending = todos.filter((t) => t.status === "pending").length;
|
|
352
|
-
const completed = todos.filter((t) => t.status === "completed").length;
|
|
353
|
-
|
|
354
|
-
return (
|
|
355
|
-
<Box flexDirection="column">
|
|
356
|
-
<Box paddingX={1} marginBottom={1}>
|
|
357
|
-
<Text dimColor>
|
|
358
|
-
{completed}/{todos.length} completed · {pending} pending
|
|
359
|
-
</Text>
|
|
360
|
-
</Box>
|
|
361
|
-
{todos.slice(0, 15).map((todo, index) => {
|
|
362
|
-
const isSelected = index === selectedIndex;
|
|
363
|
-
const color = TODO_STATUS_COLORS[todo.status];
|
|
364
|
-
const icon = TODO_STATUS_ICONS[todo.status];
|
|
365
|
-
|
|
366
|
-
return (
|
|
367
|
-
<Box key={todo.id} paddingX={1}>
|
|
368
|
-
{isSelected && <Text color="cyan">→ </Text>}
|
|
369
|
-
{!isSelected && <Text> </Text>}
|
|
370
|
-
<Text color={color}>{icon} </Text>
|
|
371
|
-
<Text
|
|
372
|
-
color={isSelected ? "cyan" : "white"}
|
|
373
|
-
dimColor={todo.status === "completed"}
|
|
374
|
-
strikethrough={todo.status === "completed"}
|
|
375
|
-
>
|
|
376
|
-
{truncateText(todo.text, 28)}
|
|
377
|
-
</Text>
|
|
378
|
-
</Box>
|
|
379
|
-
);
|
|
380
|
-
})}
|
|
381
|
-
</Box>
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// ============================================
|
|
386
|
-
// TOOLS TAB
|
|
387
|
-
// ============================================
|
|
388
|
-
|
|
389
|
-
const TOOL_STATUS_COLORS: Record<string, string> = {
|
|
390
|
-
available: "green",
|
|
391
|
-
running: "yellow",
|
|
392
|
-
error: "red",
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
const TOOL_STATUS_ICONS: Record<string, string> = {
|
|
396
|
-
available: "●",
|
|
397
|
-
running: "◐",
|
|
398
|
-
error: "○",
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
interface ToolsTabProps {
|
|
402
|
-
tools: SidebarTool[];
|
|
403
|
-
onSelect?: (toolName: string) => void;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function ToolsTab({ tools, onSelect }: ToolsTabProps) {
|
|
407
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
408
|
-
|
|
409
|
-
useEffect(() => {
|
|
410
|
-
const handleKey = (data: Buffer) => {
|
|
411
|
-
const key = data.toString();
|
|
412
|
-
|
|
413
|
-
if (key === "\x1b[A" || key === "k") {
|
|
414
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
415
|
-
} else if (key === "\x1b[B" || key === "j") {
|
|
416
|
-
setSelectedIndex((prev) => Math.min(tools.length - 1, prev + 1));
|
|
417
|
-
} else if (key === "\r" || key === "\n" || key === "l") {
|
|
418
|
-
const tool = tools[selectedIndex];
|
|
419
|
-
if (tool) {
|
|
420
|
-
onSelect?.(tool.name);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
process.stdin.on("data", handleKey);
|
|
426
|
-
return () => {
|
|
427
|
-
process.stdin.off("data", handleKey);
|
|
428
|
-
};
|
|
429
|
-
}, [tools, selectedIndex, onSelect]);
|
|
430
|
-
|
|
431
|
-
if (tools.length === 0) {
|
|
432
|
-
return (
|
|
433
|
-
<Box paddingX={1}>
|
|
434
|
-
<Text dimColor>No tools available</Text>
|
|
435
|
-
</Box>
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Group by server
|
|
440
|
-
const byServer = tools.reduce((acc, tool) => {
|
|
441
|
-
const server = tool.server;
|
|
442
|
-
if (!acc[server]) acc[server] = [];
|
|
443
|
-
acc[server].push(tool);
|
|
444
|
-
return acc;
|
|
445
|
-
}, {} as Record<string, SidebarTool[]>);
|
|
446
|
-
|
|
447
|
-
return (
|
|
448
|
-
<Box flexDirection="column">
|
|
449
|
-
{Object.entries(byServer).map(([server, serverTools]) => (
|
|
450
|
-
<Box key={server} flexDirection="column" marginBottom={1}>
|
|
451
|
-
<Box paddingX={1}>
|
|
452
|
-
<Text dimColor bold>{server}</Text>
|
|
453
|
-
</Box>
|
|
454
|
-
{serverTools.slice(0, 10).map((tool) => {
|
|
455
|
-
const index = tools.indexOf(tool);
|
|
456
|
-
const isSelected = index === selectedIndex;
|
|
457
|
-
const color = TOOL_STATUS_COLORS[tool.status];
|
|
458
|
-
const icon = TOOL_STATUS_ICONS[tool.status];
|
|
459
|
-
|
|
460
|
-
return (
|
|
461
|
-
<Box key={tool.name} paddingX={1}>
|
|
462
|
-
{isSelected && <Text color="cyan">→ </Text>}
|
|
463
|
-
{!isSelected && <Text> </Text>}
|
|
464
|
-
<Text color={color}>{icon} </Text>
|
|
465
|
-
<Text color={isSelected ? "cyan" : "white"}>
|
|
466
|
-
{tool.name}
|
|
467
|
-
</Text>
|
|
468
|
-
</Box>
|
|
469
|
-
);
|
|
470
|
-
})}
|
|
471
|
-
</Box>
|
|
472
|
-
))}
|
|
473
|
-
</Box>
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// ============================================
|
|
478
|
-
// MAIN SIDEBAR COMPONENT
|
|
479
|
-
// ============================================
|
|
480
|
-
|
|
481
|
-
export function Sidebar({
|
|
482
|
-
isOpen,
|
|
483
|
-
activeTab = "sessions",
|
|
484
|
-
width = 30,
|
|
485
|
-
sessions = [],
|
|
486
|
-
files = [],
|
|
487
|
-
todos = [],
|
|
488
|
-
tools = [],
|
|
489
|
-
currentSessionId,
|
|
490
|
-
onTabChange,
|
|
491
|
-
onSessionSelect,
|
|
492
|
-
onFileSelect,
|
|
493
|
-
onTodoToggle,
|
|
494
|
-
onToolSelect,
|
|
495
|
-
onClose,
|
|
496
|
-
}: SidebarProps) {
|
|
497
|
-
const [internalTab, setInternalTab] = useState<SidebarTab>(activeTab);
|
|
498
|
-
const { stdout } = useStdout();
|
|
499
|
-
|
|
500
|
-
useEffect(() => {
|
|
501
|
-
setInternalTab(activeTab);
|
|
502
|
-
}, [activeTab]);
|
|
503
|
-
|
|
504
|
-
// Tab change handler
|
|
505
|
-
const handleTabChange = useCallback((tab: SidebarTab) => {
|
|
506
|
-
setInternalTab(tab);
|
|
507
|
-
onTabChange?.(tab);
|
|
508
|
-
}, [onTabChange]);
|
|
509
|
-
|
|
510
|
-
if (!isOpen) {
|
|
511
|
-
return (
|
|
512
|
-
<Box width={3} borderStyle="single" borderColor="gray">
|
|
513
|
-
<Text dimColor>│</Text>
|
|
514
|
-
</Box>
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const sidebarWidth = Math.min(width, (stdout.columns || 80) - 20);
|
|
519
|
-
|
|
520
|
-
return (
|
|
521
|
-
<Box
|
|
522
|
-
flexDirection="column"
|
|
523
|
-
width={sidebarWidth}
|
|
524
|
-
borderStyle="single"
|
|
525
|
-
borderColor="gray"
|
|
526
|
-
>
|
|
527
|
-
{/* Header */}
|
|
528
|
-
<Box borderBottom borderStyle="single" borderColor="gray" paddingX={1}>
|
|
529
|
-
<Text bold color="cyan">Coder</Text>
|
|
530
|
-
<Text dimColor> | </Text>
|
|
531
|
-
<Text dimColor>Ctrl+B to close</Text>
|
|
532
|
-
</Box>
|
|
533
|
-
|
|
534
|
-
{/* Tab bar */}
|
|
535
|
-
<TabBar activeTab={internalTab} onTabChange={handleTabChange} />
|
|
536
|
-
|
|
537
|
-
{/* Tab content */}
|
|
538
|
-
<Box flexDirection="column" flexGrow={1} paddingY={1}>
|
|
539
|
-
{internalTab === "sessions" && (
|
|
540
|
-
<SessionsTab
|
|
541
|
-
sessions={sessions}
|
|
542
|
-
currentSessionId={currentSessionId}
|
|
543
|
-
onSelect={onSessionSelect}
|
|
544
|
-
/>
|
|
545
|
-
)}
|
|
546
|
-
{internalTab === "files" && (
|
|
547
|
-
<FilesTab files={files} onSelect={onFileSelect} />
|
|
548
|
-
)}
|
|
549
|
-
{internalTab === "todos" && (
|
|
550
|
-
<TodosTab todos={todos} onToggle={onTodoToggle} />
|
|
551
|
-
)}
|
|
552
|
-
{internalTab === "tools" && (
|
|
553
|
-
<ToolsTab tools={tools} onSelect={onToolSelect} />
|
|
554
|
-
)}
|
|
555
|
-
</Box>
|
|
556
|
-
|
|
557
|
-
{/* Footer */}
|
|
558
|
-
<Box borderTop borderStyle="single" borderColor="gray" paddingX={1}>
|
|
559
|
-
<Text dimColor>
|
|
560
|
-
Tab: switch | ↑↓: nav | Enter: select
|
|
561
|
-
</Text>
|
|
562
|
-
</Box>
|
|
563
|
-
</Box>
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// ============================================
|
|
568
|
-
// HELPERS
|
|
569
|
-
// ============================================
|
|
570
|
-
|
|
571
|
-
function formatTimeAgo(timestamp: number): string {
|
|
572
|
-
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
573
|
-
|
|
574
|
-
if (seconds < 60) return "just now";
|
|
575
|
-
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
576
|
-
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
577
|
-
return `${Math.floor(seconds / 86400)}d ago`;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function truncatePath(path: string, maxLength: number): string {
|
|
581
|
-
if (path.length <= maxLength) return path;
|
|
582
|
-
|
|
583
|
-
const filename = path.split("/").pop() || path;
|
|
584
|
-
if (filename.length >= maxLength - 3) {
|
|
585
|
-
return `...${filename.slice(-(maxLength - 3))}`;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const parts = path.split("/");
|
|
589
|
-
const firstPart = parts[0];
|
|
590
|
-
const remaining = parts.slice(1).join("/");
|
|
591
|
-
|
|
592
|
-
if (firstPart && remaining) {
|
|
593
|
-
return `${firstPart}/.../${parts.pop()}`;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
return `...${path.slice(-(maxLength - 3))}`;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function truncateText(text: string, maxLength: number): string {
|
|
600
|
-
if (text.length <= maxLength) return text;
|
|
601
|
-
return `${text.slice(0, maxLength - 3)}...`;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
export default Sidebar;
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TUI Components Module
|
|
3
|
-
* Composable components for building rich terminal UIs
|
|
4
|
-
*
|
|
5
|
-
* Components:
|
|
6
|
-
* - PaneManager: Resizable split pane layout
|
|
7
|
-
* - MultilineInput: Enhanced text input with autocomplete
|
|
8
|
-
* - Toast: Notification messages
|
|
9
|
-
* - Modal: Dialog overlays
|
|
10
|
-
* - SelectableList: Keyboard-navigable lists
|
|
11
|
-
* - Link: OSC 8 hyperlinks
|
|
12
|
-
* - Sidebar: Collapsible sidebar with tabs
|
|
13
|
-
*
|
|
14
|
-
* IMPORTANT: All keyboard input is handled via InputContext.
|
|
15
|
-
* Do NOT use process.stdin directly - it conflicts with the main input loop.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
// ============================================
|
|
19
|
-
// INPUT CONTEXT (centralized input management)
|
|
20
|
-
// ============================================
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
InputProvider,
|
|
24
|
-
useInputContext,
|
|
25
|
-
useInputRegistration,
|
|
26
|
-
useInputRegistration as useInputHandler,
|
|
27
|
-
useInputFocus,
|
|
28
|
-
useInputBlock,
|
|
29
|
-
InputPriority,
|
|
30
|
-
type InputHandler,
|
|
31
|
-
type InputHandlerOptions,
|
|
32
|
-
type InputContextValue,
|
|
33
|
-
type NativeKeyEvent,
|
|
34
|
-
} from "../InputContext.js";
|
|
35
|
-
|
|
36
|
-
// ============================================
|
|
37
|
-
// PANE MANAGER
|
|
38
|
-
// ============================================
|
|
39
|
-
|
|
40
|
-
export {
|
|
41
|
-
PaneManager,
|
|
42
|
-
type PaneConfig,
|
|
43
|
-
type PaneDirection,
|
|
44
|
-
type PaneManagerProps,
|
|
45
|
-
usePane,
|
|
46
|
-
PaneContext,
|
|
47
|
-
} from "./PaneManager.js";
|
|
48
|
-
|
|
49
|
-
// ============================================
|
|
50
|
-
// MULTILINE INPUT
|
|
51
|
-
// ============================================
|
|
52
|
-
|
|
53
|
-
export {
|
|
54
|
-
MultilineInput,
|
|
55
|
-
useMultilineInputHandler,
|
|
56
|
-
filterSuggestions,
|
|
57
|
-
highlightSyntax,
|
|
58
|
-
type MultilineInputProps,
|
|
59
|
-
type MultilineInputState,
|
|
60
|
-
type UseMultilineInputOptions,
|
|
61
|
-
type AutocompleteSuggestion,
|
|
62
|
-
} from "./MultilineInput.js";
|
|
63
|
-
|
|
64
|
-
// ============================================
|
|
65
|
-
// INTERACTIVE ELEMENTS
|
|
66
|
-
// ============================================
|
|
67
|
-
|
|
68
|
-
export {
|
|
69
|
-
// Toast
|
|
70
|
-
Toast,
|
|
71
|
-
useToast,
|
|
72
|
-
type ToastMessage,
|
|
73
|
-
type ToastType,
|
|
74
|
-
type ToastProps,
|
|
75
|
-
type ToastManager,
|
|
76
|
-
|
|
77
|
-
// Modal
|
|
78
|
-
Modal,
|
|
79
|
-
ConfirmModal,
|
|
80
|
-
type ModalAction,
|
|
81
|
-
type ModalProps,
|
|
82
|
-
type ConfirmModalProps,
|
|
83
|
-
|
|
84
|
-
// SelectableList
|
|
85
|
-
SelectableList,
|
|
86
|
-
type SelectableItem,
|
|
87
|
-
type SelectableListProps,
|
|
88
|
-
|
|
89
|
-
// Link
|
|
90
|
-
Link,
|
|
91
|
-
createOsc8Link,
|
|
92
|
-
type LinkProps,
|
|
93
|
-
|
|
94
|
-
// Context
|
|
95
|
-
InteractiveProvider,
|
|
96
|
-
useInteractive,
|
|
97
|
-
} from "./InteractiveElements.js";
|
|
98
|
-
|
|
99
|
-
// ============================================
|
|
100
|
-
// SIDEBAR
|
|
101
|
-
// ============================================
|
|
102
|
-
|
|
103
|
-
export {
|
|
104
|
-
Sidebar,
|
|
105
|
-
type SidebarTab,
|
|
106
|
-
type SidebarSession,
|
|
107
|
-
type SidebarFile,
|
|
108
|
-
type SidebarTodo,
|
|
109
|
-
type SidebarTool,
|
|
110
|
-
type SidebarProps,
|
|
111
|
-
} from "./Sidebar.js";
|
|
112
|
-
|
|
113
|
-
// ============================================
|
|
114
|
-
// RE-EXPORT INK COMPONENTS (convenience)
|
|
115
|
-
// ============================================
|
|
116
|
-
|
|
117
|
-
// Re-export commonly used Ink components for convenience
|
|
118
|
-
export { Box, Text, useApp, useInput, useStdout, type Key } from "ink";
|