@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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Bridge Types
|
|
3
|
+
* Type definitions for external control via TUI Bridge MCP
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PermissionMode } from "../../../../types/index.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Bridge event types that can be sent to external controllers
|
|
10
|
+
*/
|
|
11
|
+
export type BridgeEventType =
|
|
12
|
+
| "state_update"
|
|
13
|
+
| "message_added"
|
|
14
|
+
| "message_updated"
|
|
15
|
+
| "loading_changed"
|
|
16
|
+
| "model_changed"
|
|
17
|
+
| "session_changed"
|
|
18
|
+
| "command_executed"
|
|
19
|
+
| "error";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Bridge state snapshot for external controllers
|
|
23
|
+
*/
|
|
24
|
+
export interface BridgeState {
|
|
25
|
+
/** Current session ID */
|
|
26
|
+
sessionId: string;
|
|
27
|
+
/** Current model */
|
|
28
|
+
model: string;
|
|
29
|
+
/** Permission mode */
|
|
30
|
+
permissionMode: PermissionMode;
|
|
31
|
+
/** Is currently processing */
|
|
32
|
+
isLoading: boolean;
|
|
33
|
+
/** Total messages count */
|
|
34
|
+
messageCount: number;
|
|
35
|
+
/** Working directory */
|
|
36
|
+
workingDirectory: string;
|
|
37
|
+
/** Tokens used */
|
|
38
|
+
tokensUsed: number;
|
|
39
|
+
/** Total cost */
|
|
40
|
+
totalCost: number;
|
|
41
|
+
/** Timestamp */
|
|
42
|
+
timestamp: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Bridge event payload
|
|
47
|
+
*/
|
|
48
|
+
export interface BridgeEvent<T = unknown> {
|
|
49
|
+
/** Event type */
|
|
50
|
+
type: BridgeEventType;
|
|
51
|
+
/** Event payload */
|
|
52
|
+
payload: T;
|
|
53
|
+
/** Timestamp */
|
|
54
|
+
timestamp: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Bridge event map for type-safe event handling
|
|
59
|
+
*/
|
|
60
|
+
export interface BridgeEventMap {
|
|
61
|
+
state_update: BridgeState;
|
|
62
|
+
message_added: { id: string; content: string; role: "user" | "assistant" | "system" };
|
|
63
|
+
message_updated: { id: string; content: string };
|
|
64
|
+
loading_changed: { isLoading: boolean };
|
|
65
|
+
model_changed: { model: string };
|
|
66
|
+
session_changed: { sessionId: string };
|
|
67
|
+
command_executed: BridgeCommand;
|
|
68
|
+
error: { message: string; code?: string };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Command that can be sent to coder via bridge
|
|
73
|
+
*/
|
|
74
|
+
export type BridgeCommand =
|
|
75
|
+
| { type: "send_message"; content: string }
|
|
76
|
+
| { type: "execute_command"; command: string }
|
|
77
|
+
| { type: "set_model"; model: string }
|
|
78
|
+
| { type: "clear_messages" }
|
|
79
|
+
| { type: "export_session"; format: "jsonl" | "json" | "markdown" }
|
|
80
|
+
| { type: "get_state" }
|
|
81
|
+
| { type: "get_screen" };
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Bridge command result
|
|
85
|
+
*/
|
|
86
|
+
export interface BridgeCommandResult<T = unknown> {
|
|
87
|
+
/** Whether command succeeded */
|
|
88
|
+
success: boolean;
|
|
89
|
+
/** Result data if any */
|
|
90
|
+
data?: T;
|
|
91
|
+
/** Error message if failed */
|
|
92
|
+
error?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* TUI Bridge configuration
|
|
97
|
+
*/
|
|
98
|
+
export interface TUIBridgeConfig {
|
|
99
|
+
/** Enable bridge mode */
|
|
100
|
+
enabled: boolean;
|
|
101
|
+
/** Unix socket path for IPC */
|
|
102
|
+
socketPath?: string;
|
|
103
|
+
/** Port for HTTP bridge */
|
|
104
|
+
httpPort?: number;
|
|
105
|
+
/** Callback for external events */
|
|
106
|
+
onEvent?: (event: BridgeEvent) => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Screen buffer cell for TUI parsing
|
|
111
|
+
*/
|
|
112
|
+
export interface ScreenCell {
|
|
113
|
+
/** Character at this position */
|
|
114
|
+
char: string;
|
|
115
|
+
/** Foreground color */
|
|
116
|
+
fg?: string;
|
|
117
|
+
/** Background color */
|
|
118
|
+
bg?: string;
|
|
119
|
+
/** Text attributes */
|
|
120
|
+
bold?: boolean;
|
|
121
|
+
italic?: boolean;
|
|
122
|
+
underline?: boolean;
|
|
123
|
+
dim?: boolean;
|
|
124
|
+
inverse?: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Screen buffer representation
|
|
129
|
+
*/
|
|
130
|
+
export interface ScreenBuffer {
|
|
131
|
+
/** Screen width */
|
|
132
|
+
width: number;
|
|
133
|
+
/** Screen height */
|
|
134
|
+
height: number;
|
|
135
|
+
/** 2D array of cells [row][col] */
|
|
136
|
+
cells: ScreenCell[][];
|
|
137
|
+
/** Cursor position */
|
|
138
|
+
cursor: { x: number; y: number; visible: boolean };
|
|
139
|
+
/** Timestamp */
|
|
140
|
+
timestamp: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Parsed screen content
|
|
145
|
+
*/
|
|
146
|
+
export interface ParsedScreen {
|
|
147
|
+
/** Plain text content */
|
|
148
|
+
text: string;
|
|
149
|
+
/** Screen buffer */
|
|
150
|
+
buffer: ScreenBuffer;
|
|
151
|
+
/** Detected UI elements */
|
|
152
|
+
elements: UIElement[];
|
|
153
|
+
/** Timestamp */
|
|
154
|
+
timestamp: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Detected UI element types
|
|
159
|
+
*/
|
|
160
|
+
export type UIElementType =
|
|
161
|
+
| "button"
|
|
162
|
+
| "input"
|
|
163
|
+
| "menu"
|
|
164
|
+
| "menu_item"
|
|
165
|
+
| "list"
|
|
166
|
+
| "list_item"
|
|
167
|
+
| "dialog"
|
|
168
|
+
| "text"
|
|
169
|
+
| "header"
|
|
170
|
+
| "footer";
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Detected UI element in the screen
|
|
174
|
+
*/
|
|
175
|
+
export interface UIElement {
|
|
176
|
+
/** Element type */
|
|
177
|
+
type: UIElementType;
|
|
178
|
+
/** Bounding box */
|
|
179
|
+
bounds: {
|
|
180
|
+
x: number;
|
|
181
|
+
y: number;
|
|
182
|
+
width: number;
|
|
183
|
+
height: number;
|
|
184
|
+
};
|
|
185
|
+
/** Text content */
|
|
186
|
+
text: string;
|
|
187
|
+
/** Is focused/selected */
|
|
188
|
+
focused?: boolean;
|
|
189
|
+
/** Is clickable */
|
|
190
|
+
clickable?: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Bridge message format for IPC
|
|
195
|
+
*/
|
|
196
|
+
export interface BridgeMessage {
|
|
197
|
+
/** Message ID for request/response correlation */
|
|
198
|
+
id: string;
|
|
199
|
+
/** Message type */
|
|
200
|
+
type: "request" | "response" | "event";
|
|
201
|
+
/** Method name (for requests) */
|
|
202
|
+
method?: string;
|
|
203
|
+
/** Parameters (for requests) */
|
|
204
|
+
params?: unknown;
|
|
205
|
+
/** Result (for responses) */
|
|
206
|
+
result?: unknown;
|
|
207
|
+
/** Error (for error responses) */
|
|
208
|
+
error?: { code: number; message: string };
|
|
209
|
+
/** Event data (for events) */
|
|
210
|
+
event?: BridgeEvent;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Bridge methods available for external control
|
|
215
|
+
*/
|
|
216
|
+
export type BridgeMethod =
|
|
217
|
+
| "getState"
|
|
218
|
+
| "sendMessage"
|
|
219
|
+
| "executeCommand"
|
|
220
|
+
| "setModel"
|
|
221
|
+
| "clearMessages"
|
|
222
|
+
| "exportSession"
|
|
223
|
+
| "getScreen"
|
|
224
|
+
| "parseScreen"
|
|
225
|
+
| "subscribe"
|
|
226
|
+
| "unsubscribe";
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useBridge Hook
|
|
3
|
+
* React hook for TUI Bridge integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useEffect, useState, useRef } from "react";
|
|
7
|
+
import { TUIBridge } from "./index.js";
|
|
8
|
+
import type {
|
|
9
|
+
BridgeState,
|
|
10
|
+
BridgeEvent,
|
|
11
|
+
BridgeCommand,
|
|
12
|
+
BridgeCommandResult,
|
|
13
|
+
TUIBridgeConfig,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook options
|
|
18
|
+
*/
|
|
19
|
+
export interface UseBridgeOptions {
|
|
20
|
+
/** Enable bridge mode */
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
/** Unix socket path */
|
|
23
|
+
socketPath?: string;
|
|
24
|
+
/** HTTP port */
|
|
25
|
+
httpPort?: number;
|
|
26
|
+
/** Event callback */
|
|
27
|
+
onEvent?: (event: BridgeEvent) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Hook return type
|
|
32
|
+
*/
|
|
33
|
+
export interface UseBridgeReturn {
|
|
34
|
+
/** Bridge instance */
|
|
35
|
+
bridge: TUIBridge | null;
|
|
36
|
+
/** Current state */
|
|
37
|
+
state: BridgeState | null;
|
|
38
|
+
/** Is bridge enabled */
|
|
39
|
+
isEnabled: boolean;
|
|
40
|
+
/** Update state */
|
|
41
|
+
updateState: (state: Partial<BridgeState>) => void;
|
|
42
|
+
/** Execute command */
|
|
43
|
+
executeCommand: (command: BridgeCommand) => Promise<BridgeCommandResult>;
|
|
44
|
+
/** Emit event */
|
|
45
|
+
emitEvent: <T>(type: BridgeEvent<T>["type"], payload: T) => void;
|
|
46
|
+
/** Last received command */
|
|
47
|
+
lastCommand: BridgeCommand | null;
|
|
48
|
+
/** Clear last command */
|
|
49
|
+
clearLastCommand: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Hook for TUI Bridge integration
|
|
54
|
+
*/
|
|
55
|
+
export function useBridge(options: UseBridgeOptions = {}): UseBridgeReturn {
|
|
56
|
+
const { enabled = false, socketPath, httpPort, onEvent } = options;
|
|
57
|
+
|
|
58
|
+
const [state, setState] = useState<BridgeState | null>(null);
|
|
59
|
+
const [lastCommand, setLastCommand] = useState<BridgeCommand | null>(null);
|
|
60
|
+
const bridgeRef = useRef<TUIBridge | null>(null);
|
|
61
|
+
|
|
62
|
+
// Initialize bridge
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!enabled) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const config: TUIBridgeConfig = {
|
|
69
|
+
enabled,
|
|
70
|
+
socketPath,
|
|
71
|
+
httpPort,
|
|
72
|
+
onEvent: (event) => {
|
|
73
|
+
// Update local state on state_update events
|
|
74
|
+
if (event.type === "state_update") {
|
|
75
|
+
setState(event.payload as BridgeState);
|
|
76
|
+
}
|
|
77
|
+
// Call user callback
|
|
78
|
+
onEvent?.(event);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
bridgeRef.current = TUIBridge.init(config);
|
|
83
|
+
|
|
84
|
+
return () => {
|
|
85
|
+
bridgeRef.current?.destroy();
|
|
86
|
+
bridgeRef.current = null;
|
|
87
|
+
};
|
|
88
|
+
}, [enabled, socketPath, httpPort, onEvent]);
|
|
89
|
+
|
|
90
|
+
// Listen for command events
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!bridgeRef.current) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const handleCommand = (event: BridgeEvent) => {
|
|
97
|
+
if (event.type === "command_executed") {
|
|
98
|
+
setLastCommand(event.payload as BridgeCommand);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
bridgeRef.current.on("event", handleCommand);
|
|
103
|
+
|
|
104
|
+
return () => {
|
|
105
|
+
bridgeRef.current?.off("event", handleCommand);
|
|
106
|
+
};
|
|
107
|
+
}, [enabled]);
|
|
108
|
+
|
|
109
|
+
const updateState = useCallback((newState: Partial<BridgeState>) => {
|
|
110
|
+
bridgeRef.current?.updateState(newState);
|
|
111
|
+
setState((prev) => (prev ? { ...prev, ...newState } : null));
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
const executeCommand = useCallback(async (command: BridgeCommand): Promise<BridgeCommandResult> => {
|
|
115
|
+
if (!bridgeRef.current) {
|
|
116
|
+
return { success: false, error: "Bridge not initialized" };
|
|
117
|
+
}
|
|
118
|
+
return bridgeRef.current.executeCommand(command);
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
const emitEvent = useCallback(<T,>(type: BridgeEvent<T>["type"], payload: T) => {
|
|
122
|
+
bridgeRef.current?.emitEvent(type, payload);
|
|
123
|
+
}, []);
|
|
124
|
+
|
|
125
|
+
const clearLastCommand = useCallback(() => {
|
|
126
|
+
setLastCommand(null);
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
bridge: bridgeRef.current,
|
|
131
|
+
state,
|
|
132
|
+
isEnabled: enabled && bridgeRef.current !== null,
|
|
133
|
+
updateState,
|
|
134
|
+
executeCommand,
|
|
135
|
+
emitEvent,
|
|
136
|
+
lastCommand,
|
|
137
|
+
clearLastCommand,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Hook for handling bridge commands in TUI components
|
|
143
|
+
*/
|
|
144
|
+
export interface UseBridgeCommandHandlerOptions {
|
|
145
|
+
/** Bridge instance from useBridge */
|
|
146
|
+
bridge: TUIBridge | null;
|
|
147
|
+
/** Handler for send_message commands */
|
|
148
|
+
onSendMessage?: (content: string) => void;
|
|
149
|
+
/** Handler for execute_command commands */
|
|
150
|
+
onExecuteCommand?: (command: string) => void;
|
|
151
|
+
/** Handler for set_model commands */
|
|
152
|
+
onSetModel?: (model: string) => void;
|
|
153
|
+
/** Handler for clear_messages commands */
|
|
154
|
+
onClearMessages?: () => void;
|
|
155
|
+
/** Handler for export_session commands */
|
|
156
|
+
onExportSession?: (format: "jsonl" | "json" | "markdown") => void;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Hook for handling bridge commands
|
|
161
|
+
*/
|
|
162
|
+
export function useBridgeCommandHandler(options: UseBridgeCommandHandlerOptions): void {
|
|
163
|
+
const { bridge, onSendMessage, onExecuteCommand, onSetModel, onClearMessages, onExportSession } = options;
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!bridge) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const handleEvent = async (event: BridgeEvent) => {
|
|
171
|
+
if (event.type !== "command_executed") {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const command = event.payload as BridgeCommand;
|
|
176
|
+
|
|
177
|
+
switch (command.type) {
|
|
178
|
+
case "send_message":
|
|
179
|
+
onSendMessage?.(command.content);
|
|
180
|
+
break;
|
|
181
|
+
|
|
182
|
+
case "execute_command":
|
|
183
|
+
onExecuteCommand?.(command.command);
|
|
184
|
+
break;
|
|
185
|
+
|
|
186
|
+
case "set_model":
|
|
187
|
+
onSetModel?.(command.model);
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
case "clear_messages":
|
|
191
|
+
onClearMessages?.();
|
|
192
|
+
break;
|
|
193
|
+
|
|
194
|
+
case "export_session":
|
|
195
|
+
onExportSession?.(command.format);
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case "get_state":
|
|
199
|
+
// Already handled by bridge
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
bridge.on("event", handleEvent);
|
|
205
|
+
|
|
206
|
+
return () => {
|
|
207
|
+
bridge.off("event", handleEvent);
|
|
208
|
+
};
|
|
209
|
+
}, [bridge, onSendMessage, onExecuteCommand, onSetModel, onClearMessages, onExportSession]);
|
|
210
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* CLI Bootstrap - PTY Wrapper Detection
|
|
4
|
+
*
|
|
5
|
+
* This file MUST run before any other imports to handle PTY wrapping.
|
|
6
|
+
* ES modules hoist imports, so we need a separate entry point.
|
|
7
|
+
*
|
|
8
|
+
* Usage: bun bootstrap.ts [args...]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
12
|
+
import process from "node:process";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if we need PTY wrapping and handle it
|
|
16
|
+
* Returns true if we should continue to main module, false if we're the parent waiting
|
|
17
|
+
*/
|
|
18
|
+
function checkPtyWrapper(): boolean {
|
|
19
|
+
const isTTY = process.stdin.isTTY;
|
|
20
|
+
const isDoppler = !!process.env.DOPPLER_TOKEN;
|
|
21
|
+
const forceInteractive = process.env.CLAUDE_FORCE_INTERACTIVE === "true";
|
|
22
|
+
const hasQuery = process.argv.includes("-q") || process.argv.includes("--query");
|
|
23
|
+
const hasVersion = process.argv.includes("--version") || process.argv.includes("-v");
|
|
24
|
+
const hasHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
25
|
+
const alreadyWrapped = process.env.CODER_PTY_WRAPPED === "1";
|
|
26
|
+
|
|
27
|
+
// Debug output
|
|
28
|
+
if (process.env.CODER_DEBUG === "1") {
|
|
29
|
+
console.error("[bootstrap] isTTY:", isTTY);
|
|
30
|
+
console.error("[bootstrap] isDoppler:", isDoppler);
|
|
31
|
+
console.error("[bootstrap] alreadyWrapped:", alreadyWrapped);
|
|
32
|
+
console.error("[bootstrap] forceInteractive:", forceInteractive);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Skip if we have a TTY, force interactive, have a query, version, help, or already wrapped
|
|
36
|
+
if (isTTY || forceInteractive || hasQuery || hasVersion || hasHelp || alreadyWrapped) {
|
|
37
|
+
if (process.env.CODER_DEBUG === "1") {
|
|
38
|
+
console.error("[bootstrap] Skipping wrapper - conditions met");
|
|
39
|
+
}
|
|
40
|
+
return true; // Continue to main module
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// If under doppler without TTY, try to wrap with unbuffer
|
|
44
|
+
if (isDoppler) {
|
|
45
|
+
if (process.env.CODER_DEBUG === "1") {
|
|
46
|
+
console.error("[bootstrap] Attempting PTY wrap...");
|
|
47
|
+
}
|
|
48
|
+
const result = tryWrapWithUnbuffer();
|
|
49
|
+
if (process.env.CODER_DEBUG === "1") {
|
|
50
|
+
console.error("[bootstrap] Wrap result:", result.wrapped ? "success" : "failed");
|
|
51
|
+
}
|
|
52
|
+
if (result.wrapped && result.child) {
|
|
53
|
+
// Successfully wrapped - child is running
|
|
54
|
+
// Wait for child to exit, then exit parent with same code
|
|
55
|
+
const child = result.child;
|
|
56
|
+
child.on("exit", (code: number | null) => {
|
|
57
|
+
process.exit(code ?? 1);
|
|
58
|
+
});
|
|
59
|
+
child.on("error", (err: Error) => {
|
|
60
|
+
console.error("Child process error:", err.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Keep parent alive
|
|
65
|
+
process.stdin.resume();
|
|
66
|
+
setInterval(() => {
|
|
67
|
+
/* keep alive */
|
|
68
|
+
}, 60000);
|
|
69
|
+
|
|
70
|
+
return false; // Don't continue - parent just waits
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Failed to wrap - show helpful error
|
|
74
|
+
console.error("Error: Interactive mode requires a TTY.");
|
|
75
|
+
console.error("");
|
|
76
|
+
console.error("When using 'doppler run', TTY is not passed through.");
|
|
77
|
+
console.error("");
|
|
78
|
+
console.error("Install 'expect' package for automatic PTY support:");
|
|
79
|
+
console.error(" macOS: brew install expect");
|
|
80
|
+
console.error(" Ubuntu: sudo apt install expect");
|
|
81
|
+
console.error(" Fedora: sudo dnf install expect");
|
|
82
|
+
console.error("");
|
|
83
|
+
console.error("Or use one of these alternatives:");
|
|
84
|
+
console.error(" 1. Single query: doppler run -- coder -q \"your question\"");
|
|
85
|
+
console.error(" 2. Export secrets: doppler secrets download --no-file && coder");
|
|
86
|
+
console.error(" 3. Force simple: CLAUDE_FORCE_INTERACTIVE=true doppler run -- coder");
|
|
87
|
+
console.error(" 4. With unbuffer: unbuffer doppler run -- coder");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return true; // Continue to main module
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Try to wrap execution with unbuffer to provide a PTY
|
|
96
|
+
*/
|
|
97
|
+
function tryWrapWithUnbuffer(): { wrapped: boolean; child?: ReturnType<typeof spawn> } {
|
|
98
|
+
try {
|
|
99
|
+
// Check if unbuffer is available
|
|
100
|
+
const checkResult = spawnSync("which", ["unbuffer"], {
|
|
101
|
+
stdio: "ignore",
|
|
102
|
+
timeout: 1000,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (checkResult.status !== 0) {
|
|
106
|
+
return { wrapped: false };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Re-exec ourselves with unbuffer
|
|
110
|
+
const args = [process.execPath, ...process.argv.slice(1)];
|
|
111
|
+
const child = spawn("unbuffer", args, {
|
|
112
|
+
stdio: "inherit",
|
|
113
|
+
env: {
|
|
114
|
+
...process.env,
|
|
115
|
+
CODER_PTY_WRAPPED: "1",
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return { wrapped: true, child };
|
|
120
|
+
} catch {
|
|
121
|
+
return { wrapped: false };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Run PTY check and conditionally load main module
|
|
126
|
+
if (checkPtyWrapper()) {
|
|
127
|
+
// Dynamic import of main module - only after PTY check passes
|
|
128
|
+
import("./index.js").catch((err) => {
|
|
129
|
+
console.error("Failed to load main module:", err);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|
|
132
|
+
}
|