@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,422 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TUI Footer Component for Coder CLI
|
|
3
|
-
* Provides a persistent status bar at the bottom of the terminal
|
|
4
|
-
*
|
|
5
|
-
* Uses ANSI escape codes for cursor positioning:
|
|
6
|
-
* - ESC[s Save cursor position
|
|
7
|
-
* - ESC[u Restore cursor position
|
|
8
|
-
* - ESC[#;#H Move cursor to row, col
|
|
9
|
-
* - ESC[2K Clear entire line
|
|
10
|
-
* - ESC[J Clear from cursor to end of screen
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import chalk from "chalk";
|
|
14
|
-
import type { PermissionMode } from "../../../../types/index.js";
|
|
15
|
-
import {
|
|
16
|
-
calculateContextInfo,
|
|
17
|
-
formatPermissionMode,
|
|
18
|
-
type StatusLineOptions,
|
|
19
|
-
} from "../shared/status-line.js";
|
|
20
|
-
import { spinnerFrames } from "./spinner.js";
|
|
21
|
-
|
|
22
|
-
// ============================================
|
|
23
|
-
// ANSI ESCAPE CODES
|
|
24
|
-
// ============================================
|
|
25
|
-
|
|
26
|
-
const ANSI = {
|
|
27
|
-
// Cursor
|
|
28
|
-
SAVE_CURSOR: "\x1b[s",
|
|
29
|
-
RESTORE_CURSOR: "\x1b[u",
|
|
30
|
-
MOVE_TO: (row: number, col: number) => `\x1b[${row};${col}H`,
|
|
31
|
-
MOVE_TO_BOTTOM: (offset = 0) => `\x1b[999;1H\x1b[${offset + 1}A`,
|
|
32
|
-
|
|
33
|
-
// Clear
|
|
34
|
-
CLEAR_LINE: "\x1b[2K",
|
|
35
|
-
CLEAR_TO_END: "\x1b[J",
|
|
36
|
-
CLEAR_SCREEN: "\x1b[2J",
|
|
37
|
-
|
|
38
|
-
// Scrolling
|
|
39
|
-
SCROLL_UP: (lines: number) => `\x1b[${lines}S`,
|
|
40
|
-
SCROLL_DOWN: (lines: number) => `\x1b[${lines}T`,
|
|
41
|
-
|
|
42
|
-
// Colors reset
|
|
43
|
-
RESET: "\x1b[0m",
|
|
44
|
-
|
|
45
|
-
// Alternate screen buffer
|
|
46
|
-
ENTER_ALT_SCREEN: "\x1b[?1049h",
|
|
47
|
-
EXIT_ALT_SCREEN: "\x1b[?1049l",
|
|
48
|
-
|
|
49
|
-
// Cursor visibility
|
|
50
|
-
HIDE_CURSOR: "\x1b[?25l",
|
|
51
|
-
SHOW_CURSOR: "\x1b[?25h",
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Export ANSI for external use
|
|
55
|
-
export { ANSI };
|
|
56
|
-
|
|
57
|
-
// ============================================
|
|
58
|
-
// TYPES
|
|
59
|
-
// ============================================
|
|
60
|
-
|
|
61
|
-
export interface TUIFooterOptions {
|
|
62
|
-
/** Permission mode to display */
|
|
63
|
-
permissionMode: PermissionMode;
|
|
64
|
-
/** Tokens used in context */
|
|
65
|
-
tokensUsed: number;
|
|
66
|
-
/** Current model */
|
|
67
|
-
model: string;
|
|
68
|
-
/** Is loading/spinning */
|
|
69
|
-
isLoading?: boolean;
|
|
70
|
-
/** Verbose mode */
|
|
71
|
-
verbose?: boolean;
|
|
72
|
-
/** Show version */
|
|
73
|
-
showVersion?: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface TUIFooterState {
|
|
77
|
-
isEnabled: boolean;
|
|
78
|
-
lastRender: string;
|
|
79
|
-
lastOptions: TUIFooterOptions | null;
|
|
80
|
-
renderInterval: Timer | null;
|
|
81
|
-
spinnerFrame: number;
|
|
82
|
-
terminalHeight: number;
|
|
83
|
-
terminalWidth: number;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ============================================
|
|
87
|
-
// FOOTER RENDERER
|
|
88
|
-
// ============================================
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Render footer content (without positioning)
|
|
92
|
-
*/
|
|
93
|
-
function renderFooterContent(options: TUIFooterOptions, spinnerFrame?: string): string {
|
|
94
|
-
const { permissionMode, tokensUsed, model, isLoading, verbose, showVersion } = options;
|
|
95
|
-
const contextInfo = calculateContextInfo(tokensUsed, model);
|
|
96
|
-
|
|
97
|
-
// Build status parts
|
|
98
|
-
const parts: string[] = [];
|
|
99
|
-
|
|
100
|
-
// 1. Loading spinner if active
|
|
101
|
-
if (isLoading && spinnerFrame) {
|
|
102
|
-
parts.push(chalk.cyan(spinnerFrame));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 2. Context percentage with color
|
|
106
|
-
const contextValue = contextInfo.isCritical
|
|
107
|
-
? chalk.red(`${contextInfo.percentRemaining.toFixed(0)}%`)
|
|
108
|
-
: contextInfo.isLow
|
|
109
|
-
? chalk.yellow(`${contextInfo.percentRemaining.toFixed(0)}%`)
|
|
110
|
-
: chalk.dim(`${contextInfo.percentRemaining.toFixed(0)}%`);
|
|
111
|
-
parts.push(`Context: ${contextValue}`);
|
|
112
|
-
|
|
113
|
-
// 3. Permission mode
|
|
114
|
-
const permDisplay = formatPermissionMode(permissionMode);
|
|
115
|
-
parts.push(permDisplay);
|
|
116
|
-
|
|
117
|
-
// 4. Version (if verbose or showVersion)
|
|
118
|
-
if (verbose || showVersion) {
|
|
119
|
-
parts.push(chalk.dim(`v${getVersion()}`));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return parts.join(chalk.dim(" | "));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Get VERSION dynamically to avoid circular imports
|
|
127
|
-
*/
|
|
128
|
-
function getVersion(): string {
|
|
129
|
-
try {
|
|
130
|
-
// Dynamic import to avoid circular dependency
|
|
131
|
-
const statusLine = require("../shared/status-line.js");
|
|
132
|
-
return statusLine.VERSION || "0.0.0";
|
|
133
|
-
} catch {
|
|
134
|
-
return "0.0.0";
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ============================================
|
|
139
|
-
// TUI FOOTER CLASS
|
|
140
|
-
// ============================================
|
|
141
|
-
|
|
142
|
-
export class TUIFooter {
|
|
143
|
-
private state: TUIFooterState;
|
|
144
|
-
private static instance: TUIFooter | null = null;
|
|
145
|
-
|
|
146
|
-
private constructor() {
|
|
147
|
-
this.state = {
|
|
148
|
-
isEnabled: false,
|
|
149
|
-
lastRender: "",
|
|
150
|
-
lastOptions: null,
|
|
151
|
-
renderInterval: null,
|
|
152
|
-
spinnerFrame: 0,
|
|
153
|
-
terminalHeight: process.stdout.rows || 24,
|
|
154
|
-
terminalWidth: process.stdout.columns || 80,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Get singleton instance
|
|
160
|
-
*/
|
|
161
|
-
static getInstance(): TUIFooter {
|
|
162
|
-
if (!TUIFooter.instance) {
|
|
163
|
-
TUIFooter.instance = new TUIFooter();
|
|
164
|
-
}
|
|
165
|
-
return TUIFooter.instance;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Reset singleton (for testing)
|
|
170
|
-
*/
|
|
171
|
-
static reset(): void {
|
|
172
|
-
if (TUIFooter.instance) {
|
|
173
|
-
TUIFooter.instance.disable();
|
|
174
|
-
TUIFooter.instance = null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Enable the footer (start tracking terminal size)
|
|
180
|
-
*/
|
|
181
|
-
enable(): void {
|
|
182
|
-
if (this.state.isEnabled) return;
|
|
183
|
-
|
|
184
|
-
this.state.isEnabled = true;
|
|
185
|
-
this.updateTerminalSize();
|
|
186
|
-
|
|
187
|
-
// Listen for terminal resize
|
|
188
|
-
process.stdout.on("resize", this.handleResize);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Disable the footer and clear it
|
|
193
|
-
*/
|
|
194
|
-
disable(): void {
|
|
195
|
-
if (!this.state.isEnabled) return;
|
|
196
|
-
|
|
197
|
-
this.stopSpinner();
|
|
198
|
-
this.clear();
|
|
199
|
-
|
|
200
|
-
process.stdout.off("resize", this.handleResize);
|
|
201
|
-
this.state.isEnabled = false;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Check if footer is enabled
|
|
206
|
-
*/
|
|
207
|
-
isEnabled(): boolean {
|
|
208
|
-
return this.state.isEnabled;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Update terminal dimensions
|
|
213
|
-
*/
|
|
214
|
-
private updateTerminalSize(): void {
|
|
215
|
-
this.state.terminalHeight = process.stdout.rows || 24;
|
|
216
|
-
this.state.terminalWidth = process.stdout.columns || 80;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Handle terminal resize
|
|
221
|
-
*/
|
|
222
|
-
private handleResize = (): void => {
|
|
223
|
-
this.updateTerminalSize();
|
|
224
|
-
if (this.state.lastOptions) {
|
|
225
|
-
this.render(this.state.lastOptions);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Render the footer at the bottom of the screen
|
|
231
|
-
* Saves cursor position, moves to bottom, renders, restores cursor
|
|
232
|
-
*/
|
|
233
|
-
render(options: TUIFooterOptions): void {
|
|
234
|
-
if (!this.state.isEnabled) return;
|
|
235
|
-
|
|
236
|
-
this.state.lastOptions = options;
|
|
237
|
-
|
|
238
|
-
// Build footer content
|
|
239
|
-
const content = renderFooterContent(options);
|
|
240
|
-
|
|
241
|
-
// Skip if content unchanged (optimization)
|
|
242
|
-
if (content === this.state.lastRender && !options.isLoading) {
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
this.state.lastRender = content;
|
|
247
|
-
|
|
248
|
-
// Build ANSI sequence:
|
|
249
|
-
// 1. Save cursor position
|
|
250
|
-
// 2. Move to bottom of screen
|
|
251
|
-
// 3. Clear the line
|
|
252
|
-
// 4. Render footer content
|
|
253
|
-
// 5. Restore cursor position
|
|
254
|
-
const output =
|
|
255
|
-
ANSI.SAVE_CURSOR +
|
|
256
|
-
ANSI.MOVE_TO_BOTTOM() +
|
|
257
|
-
ANSI.CLEAR_LINE +
|
|
258
|
-
"\r" + // Go to start of line
|
|
259
|
-
chalk.dim("┌") +
|
|
260
|
-
content +
|
|
261
|
-
ANSI.RESTORE_CURSOR;
|
|
262
|
-
|
|
263
|
-
process.stdout.write(output);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Render with loading spinner animation
|
|
268
|
-
*/
|
|
269
|
-
renderLoading(options: TUIFooterOptions): void {
|
|
270
|
-
const frame = spinnerFrames[this.state.spinnerFrame];
|
|
271
|
-
this.state.spinnerFrame = (this.state.spinnerFrame + 1) % spinnerFrames.length;
|
|
272
|
-
|
|
273
|
-
const content = renderFooterContent(options, frame);
|
|
274
|
-
this.state.lastRender = content;
|
|
275
|
-
|
|
276
|
-
if (!this.state.isEnabled) return;
|
|
277
|
-
|
|
278
|
-
const output =
|
|
279
|
-
ANSI.SAVE_CURSOR +
|
|
280
|
-
ANSI.MOVE_TO_BOTTOM() +
|
|
281
|
-
ANSI.CLEAR_LINE +
|
|
282
|
-
"\r" +
|
|
283
|
-
chalk.dim("┌") +
|
|
284
|
-
content +
|
|
285
|
-
ANSI.RESTORE_CURSOR;
|
|
286
|
-
|
|
287
|
-
process.stdout.write(output);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Start spinner animation for loading state
|
|
292
|
-
*/
|
|
293
|
-
startSpinner(options: TUIFooterOptions, interval = 80): void {
|
|
294
|
-
this.stopSpinner();
|
|
295
|
-
|
|
296
|
-
this.state.renderInterval = setInterval(() => {
|
|
297
|
-
this.renderLoading(options);
|
|
298
|
-
}, interval);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Stop spinner animation
|
|
303
|
-
*/
|
|
304
|
-
stopSpinner(): void {
|
|
305
|
-
if (this.state.renderInterval) {
|
|
306
|
-
clearInterval(this.state.renderInterval);
|
|
307
|
-
this.state.renderInterval = null;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Clear the footer line
|
|
313
|
-
*/
|
|
314
|
-
clear(): void {
|
|
315
|
-
if (!this.state.isEnabled) return;
|
|
316
|
-
|
|
317
|
-
const output =
|
|
318
|
-
ANSI.SAVE_CURSOR +
|
|
319
|
-
ANSI.MOVE_TO_BOTTOM() +
|
|
320
|
-
ANSI.CLEAR_LINE +
|
|
321
|
-
ANSI.RESTORE_CURSOR;
|
|
322
|
-
|
|
323
|
-
process.stdout.write(output);
|
|
324
|
-
this.state.lastRender = "";
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Update just the token count (for incremental updates)
|
|
329
|
-
*/
|
|
330
|
-
updateTokens(tokensUsed: number): void {
|
|
331
|
-
if (this.state.lastOptions) {
|
|
332
|
-
this.render({
|
|
333
|
-
...this.state.lastOptions,
|
|
334
|
-
tokensUsed,
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Update loading state
|
|
341
|
-
*/
|
|
342
|
-
setLoading(isLoading: boolean): void {
|
|
343
|
-
if (this.state.lastOptions) {
|
|
344
|
-
const options = {
|
|
345
|
-
...this.state.lastOptions,
|
|
346
|
-
isLoading,
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
if (isLoading) {
|
|
350
|
-
this.startSpinner(options);
|
|
351
|
-
} else {
|
|
352
|
-
this.stopSpinner();
|
|
353
|
-
this.render(options);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Get terminal dimensions
|
|
360
|
-
*/
|
|
361
|
-
getTerminalSize(): { width: number; height: number } {
|
|
362
|
-
return {
|
|
363
|
-
width: this.state.terminalWidth,
|
|
364
|
-
height: this.state.terminalHeight,
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// ============================================
|
|
370
|
-
// CONVENIENCE EXPORTS
|
|
371
|
-
// ============================================
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Get the global TUI footer instance
|
|
375
|
-
*/
|
|
376
|
-
export function getTUIFooter(): TUIFooter {
|
|
377
|
-
return TUIFooter.getInstance();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Enable TUI footer
|
|
382
|
-
*/
|
|
383
|
-
export function enableTUIFooter(): TUIFooter {
|
|
384
|
-
const footer = TUIFooter.getInstance();
|
|
385
|
-
footer.enable();
|
|
386
|
-
return footer;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Disable TUI footer
|
|
391
|
-
*/
|
|
392
|
-
export function disableTUIFooter(): void {
|
|
393
|
-
TUIFooter.getInstance().disable();
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Render footer status (convenience function)
|
|
398
|
-
*/
|
|
399
|
-
export function renderTUIFooter(options: TUIFooterOptions): void {
|
|
400
|
-
TUIFooter.getInstance().render(options);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Clear the footer line
|
|
405
|
-
*/
|
|
406
|
-
export function clearTUIFooter(): void {
|
|
407
|
-
TUIFooter.getInstance().clear();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// ============================================
|
|
411
|
-
// EXPORTS
|
|
412
|
-
// ============================================
|
|
413
|
-
|
|
414
|
-
export default {
|
|
415
|
-
TUIFooter,
|
|
416
|
-
getTUIFooter,
|
|
417
|
-
enableTUIFooter,
|
|
418
|
-
disableTUIFooter,
|
|
419
|
-
renderTUIFooter,
|
|
420
|
-
clearTUIFooter,
|
|
421
|
-
ANSI,
|
|
422
|
-
};
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TUI Types
|
|
3
|
-
* Type definitions for the interactive TUI components
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { PermissionMode, ClaudeModel, Message as ApiMessage, ToolDefinition } from "../../../../types/index.js";
|
|
7
|
-
import type { HookManager } from "../../../../ecosystem/hooks/index.js";
|
|
8
|
-
import type { TerminalHandle } from "../../../../native/index.js";
|
|
9
|
-
import type { LoadedSession, SessionSummary } from "../../../../core/sessions/types.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Message sub-type for granular categorization
|
|
13
|
-
* - tool_call: Assistant is invoking a tool
|
|
14
|
-
* - tool_result: Tool execution result (success or error)
|
|
15
|
-
* - hook: Hook intercept/decision message
|
|
16
|
-
* - info: General system info (session start, commands, etc.)
|
|
17
|
-
* - error: Error messages
|
|
18
|
-
* - thinking: Extended thinking output
|
|
19
|
-
*/
|
|
20
|
-
export type MessageSubType =
|
|
21
|
-
| "tool_call"
|
|
22
|
-
| "tool_result"
|
|
23
|
-
| "hook"
|
|
24
|
-
| "info"
|
|
25
|
-
| "error"
|
|
26
|
-
| "thinking";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* UI-specific message representation
|
|
30
|
-
*/
|
|
31
|
-
export interface UIMessage {
|
|
32
|
-
id: string;
|
|
33
|
-
/** Base role: user, assistant, or system */
|
|
34
|
-
role: "user" | "assistant" | "system";
|
|
35
|
-
/** Message content for display */
|
|
36
|
-
content: string;
|
|
37
|
-
/** Timestamp in milliseconds */
|
|
38
|
-
timestamp: number;
|
|
39
|
-
/** Optional sub-type for granular display labels */
|
|
40
|
-
subType?: MessageSubType;
|
|
41
|
-
/** For tool messages: the tool name */
|
|
42
|
-
toolName?: string;
|
|
43
|
-
/** For tool_result: whether it was an error */
|
|
44
|
-
isError?: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Props for the main InteractiveTUI component
|
|
49
|
-
*/
|
|
50
|
-
export interface InteractiveTUIProps {
|
|
51
|
-
apiKey: string;
|
|
52
|
-
model: ClaudeModel;
|
|
53
|
-
permissionMode: PermissionMode;
|
|
54
|
-
maxTokens: number;
|
|
55
|
-
systemPrompt: string;
|
|
56
|
-
tools: ToolDefinition[];
|
|
57
|
-
hookManager: HookManager;
|
|
58
|
-
sessionStore: SessionStore;
|
|
59
|
-
sessionId: string;
|
|
60
|
-
setSessionId: (id: string) => void;
|
|
61
|
-
initialMessages: ApiMessage[];
|
|
62
|
-
workingDirectory: string;
|
|
63
|
-
onExit: () => void;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Session store interface (subset used by TUI)
|
|
68
|
-
*/
|
|
69
|
-
export interface SessionStore {
|
|
70
|
-
saveMessage(message: ApiMessage): Promise<void>;
|
|
71
|
-
saveMetrics(metrics: unknown): Promise<void>;
|
|
72
|
-
exportSession(sessionId: string, format: "jsonl" | "json" | "markdown"): Promise<string>;
|
|
73
|
-
listSessions(limit?: number): Promise<SessionSummary[]>;
|
|
74
|
-
resumeSession(sessionId: string): Promise<LoadedSession | null>;
|
|
75
|
-
createSession(options: {
|
|
76
|
-
model: string;
|
|
77
|
-
workingDirectory: string;
|
|
78
|
-
agentName?: string;
|
|
79
|
-
agentColor?: string;
|
|
80
|
-
teamName?: string;
|
|
81
|
-
}): Promise<string>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Session info for listing
|
|
86
|
-
*/
|
|
87
|
-
export interface SessionInfo {
|
|
88
|
-
id: string;
|
|
89
|
-
messageCount: number;
|
|
90
|
-
lastActivity?: number;
|
|
91
|
-
metadata?: Record<string, unknown>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Message area component props
|
|
96
|
-
*/
|
|
97
|
-
export interface MessageAreaProps {
|
|
98
|
-
messages: UIMessage[];
|
|
99
|
-
isLoading: boolean;
|
|
100
|
-
spinnerFrame: string;
|
|
101
|
-
height: number;
|
|
102
|
-
scrollOffset: number;
|
|
103
|
-
contextWarning: string | null;
|
|
104
|
-
streamingText?: string;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Status bar component props
|
|
109
|
-
*/
|
|
110
|
-
export interface StatusBarProps {
|
|
111
|
-
permissionMode: PermissionMode;
|
|
112
|
-
tokensUsed: number;
|
|
113
|
-
model: string;
|
|
114
|
-
isLoading: boolean;
|
|
115
|
-
spinnerFrame: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Input field component props
|
|
120
|
-
*/
|
|
121
|
-
export interface InputFieldProps {
|
|
122
|
-
value: string;
|
|
123
|
-
cursorPos: number;
|
|
124
|
-
placeholder: string;
|
|
125
|
-
isActive: boolean;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Terminal layout configuration
|
|
130
|
-
*/
|
|
131
|
-
export interface TerminalLayout {
|
|
132
|
-
terminalHeight: number;
|
|
133
|
-
inputHeight: number;
|
|
134
|
-
statusHeight: number;
|
|
135
|
-
messageHeight: number;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Command handler context
|
|
140
|
-
*/
|
|
141
|
-
export interface CommandContext {
|
|
142
|
-
sessionId: string;
|
|
143
|
-
setSessionId: (id: string) => void;
|
|
144
|
-
model: ClaudeModel;
|
|
145
|
-
setModel: (model: ClaudeModel) => void;
|
|
146
|
-
apiMessages: ApiMessage[];
|
|
147
|
-
setApiMessages: (messages: ApiMessage[]) => void;
|
|
148
|
-
setMessages: (messages: UIMessage[]) => void;
|
|
149
|
-
processedCountRef: React.MutableRefObject<number>;
|
|
150
|
-
totalCost: number;
|
|
151
|
-
setTotalCost: (cost: number) => void;
|
|
152
|
-
totalTokens: number;
|
|
153
|
-
setTotalTokens: (tokens: number) => void;
|
|
154
|
-
permissionMode: PermissionMode;
|
|
155
|
-
tools: ToolDefinition[];
|
|
156
|
-
workingDirectory: string;
|
|
157
|
-
sessionStore: SessionStore;
|
|
158
|
-
addSystemMessage: (content: string) => void;
|
|
159
|
-
messagesLength: number;
|
|
160
|
-
onExit: () => void;
|
|
161
|
-
exit: () => void;
|
|
162
|
-
// Session selection state
|
|
163
|
-
sessionSelectMode: boolean;
|
|
164
|
-
setSessionSelectMode: (mode: boolean) => void;
|
|
165
|
-
setSelectableSessions: (sessions: SessionInfo[]) => void;
|
|
166
|
-
// Help mode state
|
|
167
|
-
helpMode: boolean;
|
|
168
|
-
setHelpMode: (mode: boolean) => void;
|
|
169
|
-
helpSection: number;
|
|
170
|
-
setHelpSection: (section: number) => void;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Context info from status-line
|
|
175
|
-
*/
|
|
176
|
-
export interface ContextInfo {
|
|
177
|
-
percentRemaining: number;
|
|
178
|
-
isLow: boolean;
|
|
179
|
-
isCritical: boolean;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Re-export types from parent for convenience
|
|
184
|
-
*/
|
|
185
|
-
export type { PermissionMode, ClaudeModel, Message as ApiMessage, ToolDefinition } from "../../../../types/index.js";
|
|
186
|
-
export type { HookManager } from "../../../../ecosystem/hooks/index.js";
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TUI Input Handler Hook
|
|
3
|
-
* Custom hook for handling keyboard input in TUI
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useCallback } from "react";
|
|
7
|
-
import { useInput } from "ink";
|
|
8
|
-
|
|
9
|
-
interface UseInputHandlerOptions {
|
|
10
|
-
isLoading: boolean;
|
|
11
|
-
inputValue: string;
|
|
12
|
-
cursorPos: number;
|
|
13
|
-
setInputValue: (value: string | ((prev: string) => string)) => void;
|
|
14
|
-
setCursorPos: (value: number | ((prev: number) => number)) => void;
|
|
15
|
-
onSubmit: (value: string) => void;
|
|
16
|
-
onCommand: (cmd: string) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Hook for handling keyboard input in the TUI
|
|
21
|
-
*/
|
|
22
|
-
export function useInputHandler({
|
|
23
|
-
isLoading,
|
|
24
|
-
inputValue,
|
|
25
|
-
cursorPos,
|
|
26
|
-
setInputValue,
|
|
27
|
-
setCursorPos,
|
|
28
|
-
onSubmit,
|
|
29
|
-
onCommand,
|
|
30
|
-
}: UseInputHandlerOptions) {
|
|
31
|
-
// Main input handler
|
|
32
|
-
useInput(
|
|
33
|
-
(input, key) => {
|
|
34
|
-
if (isLoading) return;
|
|
35
|
-
|
|
36
|
-
// Submit on Enter
|
|
37
|
-
if (key.return) {
|
|
38
|
-
if (inputValue.trim()) {
|
|
39
|
-
if (inputValue.startsWith("/")) {
|
|
40
|
-
onCommand(inputValue);
|
|
41
|
-
} else {
|
|
42
|
-
onSubmit(inputValue);
|
|
43
|
-
}
|
|
44
|
-
setInputValue("");
|
|
45
|
-
setCursorPos(0);
|
|
46
|
-
}
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Backspace
|
|
51
|
-
if (key.backspace || key.delete) {
|
|
52
|
-
if (cursorPos > 0) {
|
|
53
|
-
setInputValue((v) => v.slice(0, cursorPos - 1) + v.slice(cursorPos));
|
|
54
|
-
setCursorPos((p) => p - 1);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Arrow keys
|
|
60
|
-
if (key.leftArrow) {
|
|
61
|
-
setCursorPos((p) => Math.max(0, p - 1));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (key.rightArrow) {
|
|
66
|
-
setCursorPos((p) => Math.min(inputValue.length, p + 1));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Home/End keys (Ctrl+A / Ctrl+E)
|
|
71
|
-
if (key.ctrl && input === "a") {
|
|
72
|
-
setCursorPos(0);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (key.ctrl && input === "e") {
|
|
77
|
-
setCursorPos(inputValue.length);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Regular character
|
|
82
|
-
if (input && !key.ctrl && !key.meta) {
|
|
83
|
-
setInputValue((v) => v.slice(0, cursorPos) + input + v.slice(cursorPos));
|
|
84
|
-
setCursorPos((p) => p + input.length);
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
{ isActive: !isLoading }
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Hook for handling exit shortcuts (Ctrl+C)
|
|
93
|
-
*/
|
|
94
|
-
export function useExitHandler(onExit: () => void, exit: () => void) {
|
|
95
|
-
useInput(
|
|
96
|
-
(input, key) => {
|
|
97
|
-
if (key.ctrl && input === "c") {
|
|
98
|
-
onExit();
|
|
99
|
-
exit();
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
{ isActive: true }
|
|
103
|
-
);
|
|
104
|
-
}
|