ralph-cli-sandboxed 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/commands/action.js +47 -20
- package/dist/commands/chat.d.ts +1 -1
- package/dist/commands/chat.js +325 -62
- package/dist/commands/config.js +2 -1
- package/dist/commands/daemon.d.ts +2 -5
- package/dist/commands/daemon.js +118 -49
- package/dist/commands/docker.js +110 -73
- package/dist/commands/fix-config.js +2 -1
- package/dist/commands/fix-prd.js +2 -2
- package/dist/commands/help.js +19 -3
- package/dist/commands/init.js +78 -17
- package/dist/commands/listen.js +116 -5
- package/dist/commands/logo.d.ts +5 -0
- package/dist/commands/logo.js +41 -0
- package/dist/commands/notify.js +1 -1
- package/dist/commands/once.js +19 -9
- package/dist/commands/prd.js +20 -2
- package/dist/commands/run.js +111 -27
- package/dist/commands/slack.d.ts +10 -0
- package/dist/commands/slack.js +333 -0
- package/dist/config/responder-presets.json +69 -0
- package/dist/index.js +6 -1
- package/dist/providers/discord.d.ts +82 -0
- package/dist/providers/discord.js +697 -0
- package/dist/providers/slack.d.ts +79 -0
- package/dist/providers/slack.js +715 -0
- package/dist/providers/telegram.d.ts +30 -0
- package/dist/providers/telegram.js +190 -7
- package/dist/responders/claude-code-responder.d.ts +48 -0
- package/dist/responders/claude-code-responder.js +203 -0
- package/dist/responders/cli-responder.d.ts +62 -0
- package/dist/responders/cli-responder.js +298 -0
- package/dist/responders/llm-responder.d.ts +135 -0
- package/dist/responders/llm-responder.js +582 -0
- package/dist/templates/macos-scripts.js +2 -4
- package/dist/templates/prompts.js +4 -2
- package/dist/tui/ConfigEditor.js +42 -5
- package/dist/tui/components/ArrayEditor.js +1 -1
- package/dist/tui/components/EditorPanel.js +10 -6
- package/dist/tui/components/HelpPanel.d.ts +1 -1
- package/dist/tui/components/HelpPanel.js +1 -1
- package/dist/tui/components/JsonSnippetEditor.js +8 -5
- package/dist/tui/components/KeyValueEditor.js +69 -5
- package/dist/tui/components/LLMProvidersEditor.d.ts +22 -0
- package/dist/tui/components/LLMProvidersEditor.js +357 -0
- package/dist/tui/components/ObjectEditor.js +1 -1
- package/dist/tui/components/Preview.js +1 -1
- package/dist/tui/components/RespondersEditor.d.ts +22 -0
- package/dist/tui/components/RespondersEditor.js +437 -0
- package/dist/tui/components/SectionNav.js +27 -3
- package/dist/tui/utils/presets.js +15 -2
- package/dist/utils/chat-client.d.ts +33 -4
- package/dist/utils/chat-client.js +20 -1
- package/dist/utils/config.d.ts +100 -1
- package/dist/utils/config.js +78 -1
- package/dist/utils/daemon-actions.d.ts +19 -0
- package/dist/utils/daemon-actions.js +111 -0
- package/dist/utils/daemon-client.d.ts +21 -0
- package/dist/utils/daemon-client.js +28 -1
- package/dist/utils/llm-client.d.ts +82 -0
- package/dist/utils/llm-client.js +185 -0
- package/dist/utils/message-queue.js +6 -6
- package/dist/utils/notification.d.ts +10 -2
- package/dist/utils/notification.js +111 -4
- package/dist/utils/prd-validator.js +60 -19
- package/dist/utils/prompt.js +22 -12
- package/dist/utils/responder-logger.d.ts +47 -0
- package/dist/utils/responder-logger.js +129 -0
- package/dist/utils/responder-presets.d.ts +92 -0
- package/dist/utils/responder-presets.js +156 -0
- package/dist/utils/responder.d.ts +88 -0
- package/dist/utils/responder.js +207 -0
- package/dist/utils/stream-json.js +6 -6
- package/docs/CHAT-CLIENTS.md +520 -0
- package/docs/CHAT-RESPONDERS.md +785 -0
- package/docs/DEVELOPMENT.md +25 -0
- package/docs/USEFUL_ACTIONS.md +815 -0
- package/docs/chat-architecture.md +251 -0
- package/package.json +14 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState, useCallback, useMemo } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
/**
|
|
6
|
+
* Provider type options for dropdown.
|
|
7
|
+
*/
|
|
8
|
+
const PROVIDER_TYPES = ["anthropic", "openai", "ollama"];
|
|
9
|
+
/**
|
|
10
|
+
* Default models for each provider type.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_MODELS = {
|
|
13
|
+
anthropic: "claude-sonnet-4-20250514",
|
|
14
|
+
openai: "gpt-4o",
|
|
15
|
+
ollama: "llama3",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Model suggestions for each provider type.
|
|
19
|
+
*/
|
|
20
|
+
const MODEL_SUGGESTIONS = {
|
|
21
|
+
anthropic: ["claude-sonnet-4-20250514", "claude-opus-4-20250514", "claude-3-5-haiku-20241022"],
|
|
22
|
+
openai: ["gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo", "o1-preview", "o1-mini"],
|
|
23
|
+
ollama: ["llama3", "llama3.1", "mistral", "codellama", "mixtral", "phi"],
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* LLMProvidersEditor component for editing LLM provider configurations.
|
|
27
|
+
* Provides a user-friendly interface for adding, editing, and removing LLM providers.
|
|
28
|
+
*/
|
|
29
|
+
export function LLMProvidersEditor({ label, providers, onConfirm, onCancel, isFocused = true, maxHeight = 15, }) {
|
|
30
|
+
const [editProviders, setEditProviders] = useState({ ...providers });
|
|
31
|
+
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
32
|
+
const [mode, setMode] = useState("list");
|
|
33
|
+
const [editText, setEditText] = useState("");
|
|
34
|
+
const [editingProvider, setEditingProvider] = useState(null);
|
|
35
|
+
const [typeIndex, setTypeIndex] = useState(0);
|
|
36
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
37
|
+
// Get sorted provider names
|
|
38
|
+
const providerNames = useMemo(() => Object.keys(editProviders).sort(), [editProviders]);
|
|
39
|
+
// Total options includes all providers plus "+ Add provider" option
|
|
40
|
+
const totalOptions = providerNames.length + 1;
|
|
41
|
+
// Calculate visible range for scrolling
|
|
42
|
+
const visibleCount = Math.min(maxHeight - 6, totalOptions); // Reserve lines for header, footer, hints
|
|
43
|
+
const visibleProviders = useMemo(() => {
|
|
44
|
+
const endIndex = Math.min(scrollOffset + visibleCount, providerNames.length);
|
|
45
|
+
return providerNames.slice(scrollOffset, endIndex);
|
|
46
|
+
}, [scrollOffset, visibleCount, providerNames]);
|
|
47
|
+
// Auto-scroll to keep highlighted item visible
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
if (highlightedIndex < scrollOffset) {
|
|
50
|
+
setScrollOffset(highlightedIndex);
|
|
51
|
+
}
|
|
52
|
+
else if (highlightedIndex >= scrollOffset + visibleCount) {
|
|
53
|
+
setScrollOffset(Math.max(0, highlightedIndex - visibleCount + 1));
|
|
54
|
+
}
|
|
55
|
+
}, [highlightedIndex, scrollOffset, visibleCount]);
|
|
56
|
+
// Navigation handlers
|
|
57
|
+
const handleNavigateUp = useCallback(() => {
|
|
58
|
+
setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : totalOptions - 1));
|
|
59
|
+
}, [totalOptions]);
|
|
60
|
+
const handleNavigateDown = useCallback(() => {
|
|
61
|
+
setHighlightedIndex((prev) => (prev < totalOptions - 1 ? prev + 1 : 0));
|
|
62
|
+
}, [totalOptions]);
|
|
63
|
+
// Delete the highlighted provider
|
|
64
|
+
const handleDelete = useCallback(() => {
|
|
65
|
+
if (highlightedIndex < providerNames.length) {
|
|
66
|
+
const nameToDelete = providerNames[highlightedIndex];
|
|
67
|
+
const newProviders = { ...editProviders };
|
|
68
|
+
delete newProviders[nameToDelete];
|
|
69
|
+
setEditProviders(newProviders);
|
|
70
|
+
// Adjust highlighted index if needed
|
|
71
|
+
const newNames = Object.keys(newProviders);
|
|
72
|
+
if (highlightedIndex >= newNames.length && newNames.length > 0) {
|
|
73
|
+
setHighlightedIndex(newNames.length - 1);
|
|
74
|
+
}
|
|
75
|
+
else if (newNames.length === 0) {
|
|
76
|
+
setHighlightedIndex(0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, [highlightedIndex, providerNames, editProviders]);
|
|
80
|
+
// Start editing or adding a provider
|
|
81
|
+
const handleStartEdit = useCallback(() => {
|
|
82
|
+
if (highlightedIndex < providerNames.length) {
|
|
83
|
+
// Edit existing provider
|
|
84
|
+
const name = providerNames[highlightedIndex];
|
|
85
|
+
const config = editProviders[name];
|
|
86
|
+
setEditingProvider({ name, config: { ...config } });
|
|
87
|
+
setMode("edit-provider");
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Add new provider - start with name input
|
|
91
|
+
setEditText("");
|
|
92
|
+
setMode("add-name");
|
|
93
|
+
}
|
|
94
|
+
}, [highlightedIndex, providerNames, editProviders]);
|
|
95
|
+
// Handle name submission when adding new provider
|
|
96
|
+
const handleNameSubmit = useCallback(() => {
|
|
97
|
+
const trimmedName = editText.trim();
|
|
98
|
+
if (trimmedName) {
|
|
99
|
+
// Check if name already exists
|
|
100
|
+
if (editProviders[trimmedName]) {
|
|
101
|
+
// Edit existing instead
|
|
102
|
+
const config = editProviders[trimmedName];
|
|
103
|
+
setEditingProvider({ name: trimmedName, config: { ...config } });
|
|
104
|
+
setMode("edit-provider");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Create new provider with default values
|
|
108
|
+
setEditingProvider({
|
|
109
|
+
name: trimmedName,
|
|
110
|
+
config: {
|
|
111
|
+
type: "anthropic",
|
|
112
|
+
model: DEFAULT_MODELS.anthropic,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
setTypeIndex(0);
|
|
116
|
+
setMode("select-type");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
setMode("list");
|
|
121
|
+
}
|
|
122
|
+
setEditText("");
|
|
123
|
+
}, [editText, editProviders]);
|
|
124
|
+
// Handle type selection
|
|
125
|
+
const handleTypeSelect = useCallback(() => {
|
|
126
|
+
if (editingProvider) {
|
|
127
|
+
const selectedType = PROVIDER_TYPES[typeIndex];
|
|
128
|
+
setEditingProvider({
|
|
129
|
+
...editingProvider,
|
|
130
|
+
config: {
|
|
131
|
+
...editingProvider.config,
|
|
132
|
+
type: selectedType,
|
|
133
|
+
model: DEFAULT_MODELS[selectedType],
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
setEditText(DEFAULT_MODELS[selectedType]);
|
|
137
|
+
setMode("edit-model");
|
|
138
|
+
}
|
|
139
|
+
}, [editingProvider, typeIndex]);
|
|
140
|
+
// Handle model submission
|
|
141
|
+
const handleModelSubmit = useCallback(() => {
|
|
142
|
+
if (editingProvider) {
|
|
143
|
+
const trimmedModel = editText.trim() || DEFAULT_MODELS[editingProvider.config.type];
|
|
144
|
+
setEditingProvider({
|
|
145
|
+
...editingProvider,
|
|
146
|
+
config: {
|
|
147
|
+
...editingProvider.config,
|
|
148
|
+
model: trimmedModel,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
// For ollama, skip API key and go to baseUrl
|
|
152
|
+
if (editingProvider.config.type === "ollama") {
|
|
153
|
+
setEditText(editingProvider.config.baseUrl || "http://localhost:11434");
|
|
154
|
+
setMode("edit-baseurl");
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
setEditText(editingProvider.config.apiKey || "");
|
|
158
|
+
setMode("edit-apikey");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}, [editingProvider, editText]);
|
|
162
|
+
// Handle API key submission
|
|
163
|
+
const handleApiKeySubmit = useCallback(() => {
|
|
164
|
+
if (editingProvider) {
|
|
165
|
+
const apiKey = editText.trim() || undefined;
|
|
166
|
+
setEditingProvider({
|
|
167
|
+
...editingProvider,
|
|
168
|
+
config: {
|
|
169
|
+
...editingProvider.config,
|
|
170
|
+
apiKey,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
setEditText(editingProvider.config.baseUrl || "");
|
|
174
|
+
setMode("edit-baseurl");
|
|
175
|
+
}
|
|
176
|
+
}, [editingProvider, editText]);
|
|
177
|
+
// Handle base URL submission and save provider
|
|
178
|
+
const handleBaseUrlSubmit = useCallback(() => {
|
|
179
|
+
if (editingProvider) {
|
|
180
|
+
const baseUrl = editText.trim() || undefined;
|
|
181
|
+
const newProviders = {
|
|
182
|
+
...editProviders,
|
|
183
|
+
[editingProvider.name]: {
|
|
184
|
+
...editingProvider.config,
|
|
185
|
+
baseUrl,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
// Clean up undefined values
|
|
189
|
+
if (!newProviders[editingProvider.name].apiKey) {
|
|
190
|
+
delete newProviders[editingProvider.name].apiKey;
|
|
191
|
+
}
|
|
192
|
+
if (!newProviders[editingProvider.name].baseUrl) {
|
|
193
|
+
delete newProviders[editingProvider.name].baseUrl;
|
|
194
|
+
}
|
|
195
|
+
setEditProviders(newProviders);
|
|
196
|
+
setEditingProvider(null);
|
|
197
|
+
setMode("list");
|
|
198
|
+
setEditText("");
|
|
199
|
+
// Update highlighted index to the new/edited provider
|
|
200
|
+
const sortedNames = Object.keys(newProviders).sort();
|
|
201
|
+
const newIndex = sortedNames.indexOf(editingProvider.name);
|
|
202
|
+
if (newIndex >= 0) {
|
|
203
|
+
setHighlightedIndex(newIndex);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}, [editingProvider, editText, editProviders]);
|
|
207
|
+
// Cancel editing
|
|
208
|
+
const handleCancel = useCallback(() => {
|
|
209
|
+
setMode("list");
|
|
210
|
+
setEditText("");
|
|
211
|
+
setEditingProvider(null);
|
|
212
|
+
}, []);
|
|
213
|
+
// Handle keyboard input for list mode
|
|
214
|
+
useInput((input, key) => {
|
|
215
|
+
if (!isFocused || mode !== "list")
|
|
216
|
+
return;
|
|
217
|
+
if (input === "j" || key.downArrow) {
|
|
218
|
+
handleNavigateDown();
|
|
219
|
+
}
|
|
220
|
+
else if (input === "k" || key.upArrow) {
|
|
221
|
+
handleNavigateUp();
|
|
222
|
+
}
|
|
223
|
+
else if (key.return || input === "e") {
|
|
224
|
+
handleStartEdit();
|
|
225
|
+
}
|
|
226
|
+
else if (input === "d" || key.delete) {
|
|
227
|
+
handleDelete();
|
|
228
|
+
}
|
|
229
|
+
else if (key.escape) {
|
|
230
|
+
onCancel();
|
|
231
|
+
}
|
|
232
|
+
else if (input === "s" || input === "S") {
|
|
233
|
+
onConfirm(editProviders);
|
|
234
|
+
}
|
|
235
|
+
}, { isActive: isFocused && mode === "list" });
|
|
236
|
+
// Handle keyboard input for type selection
|
|
237
|
+
useInput((input, key) => {
|
|
238
|
+
if (!isFocused || mode !== "select-type")
|
|
239
|
+
return;
|
|
240
|
+
if (input === "j" || key.downArrow) {
|
|
241
|
+
setTypeIndex((prev) => (prev < PROVIDER_TYPES.length - 1 ? prev + 1 : 0));
|
|
242
|
+
}
|
|
243
|
+
else if (input === "k" || key.upArrow) {
|
|
244
|
+
setTypeIndex((prev) => (prev > 0 ? prev - 1 : PROVIDER_TYPES.length - 1));
|
|
245
|
+
}
|
|
246
|
+
else if (key.return) {
|
|
247
|
+
handleTypeSelect();
|
|
248
|
+
}
|
|
249
|
+
else if (key.escape) {
|
|
250
|
+
handleCancel();
|
|
251
|
+
}
|
|
252
|
+
}, { isActive: isFocused && mode === "select-type" });
|
|
253
|
+
// Handle keyboard input for text editing modes
|
|
254
|
+
useInput((_input, key) => {
|
|
255
|
+
if (!isFocused ||
|
|
256
|
+
mode === "list" ||
|
|
257
|
+
mode === "select-type" ||
|
|
258
|
+
mode === "edit-provider")
|
|
259
|
+
return;
|
|
260
|
+
if (key.escape) {
|
|
261
|
+
handleCancel();
|
|
262
|
+
}
|
|
263
|
+
}, {
|
|
264
|
+
isActive: isFocused &&
|
|
265
|
+
mode !== "list" &&
|
|
266
|
+
mode !== "select-type" &&
|
|
267
|
+
mode !== "edit-provider",
|
|
268
|
+
});
|
|
269
|
+
// Handle keyboard input for edit-provider mode (viewing a provider)
|
|
270
|
+
useInput((input, key) => {
|
|
271
|
+
if (!isFocused || mode !== "edit-provider" || !editingProvider)
|
|
272
|
+
return;
|
|
273
|
+
if (key.escape) {
|
|
274
|
+
handleCancel();
|
|
275
|
+
}
|
|
276
|
+
else if (input === "t" || input === "T") {
|
|
277
|
+
// Edit type
|
|
278
|
+
setTypeIndex(PROVIDER_TYPES.indexOf(editingProvider.config.type));
|
|
279
|
+
setMode("select-type");
|
|
280
|
+
}
|
|
281
|
+
else if (input === "m" || input === "M") {
|
|
282
|
+
// Edit model
|
|
283
|
+
setEditText(editingProvider.config.model);
|
|
284
|
+
setMode("edit-model");
|
|
285
|
+
}
|
|
286
|
+
else if (input === "a" || input === "A") {
|
|
287
|
+
// Edit API key
|
|
288
|
+
setEditText(editingProvider.config.apiKey || "");
|
|
289
|
+
setMode("edit-apikey");
|
|
290
|
+
}
|
|
291
|
+
else if (input === "b" || input === "B") {
|
|
292
|
+
// Edit base URL
|
|
293
|
+
setEditText(editingProvider.config.baseUrl || "");
|
|
294
|
+
setMode("edit-baseurl");
|
|
295
|
+
}
|
|
296
|
+
else if (input === "s" || input === "S") {
|
|
297
|
+
// Save and close
|
|
298
|
+
const newProviders = {
|
|
299
|
+
...editProviders,
|
|
300
|
+
[editingProvider.name]: { ...editingProvider.config },
|
|
301
|
+
};
|
|
302
|
+
// Clean up undefined values
|
|
303
|
+
if (!newProviders[editingProvider.name].apiKey) {
|
|
304
|
+
delete newProviders[editingProvider.name].apiKey;
|
|
305
|
+
}
|
|
306
|
+
if (!newProviders[editingProvider.name].baseUrl) {
|
|
307
|
+
delete newProviders[editingProvider.name].baseUrl;
|
|
308
|
+
}
|
|
309
|
+
setEditProviders(newProviders);
|
|
310
|
+
setEditingProvider(null);
|
|
311
|
+
setMode("list");
|
|
312
|
+
}
|
|
313
|
+
}, { isActive: isFocused && mode === "edit-provider" });
|
|
314
|
+
// Render type selection mode
|
|
315
|
+
if (mode === "select-type") {
|
|
316
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Select Provider Type" }), editingProvider && _jsxs(Text, { dimColor: true, children: [" for \"", editingProvider.name, "\""] })] }), PROVIDER_TYPES.map((type, index) => {
|
|
317
|
+
const isHighlighted = index === typeIndex;
|
|
318
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? "cyan" : undefined, children: isHighlighted ? "▸ " : " " }), _jsx(Text, { bold: isHighlighted, color: isHighlighted ? "cyan" : undefined, inverse: isHighlighted, children: type }), _jsxs(Text, { dimColor: true, children: [" - ", type === "anthropic" ? "Claude models" : type === "openai" ? "GPT models" : "Local models"] })] }, type));
|
|
319
|
+
}), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "j/k: navigate | Enter: select | Esc: cancel" }) })] }));
|
|
320
|
+
}
|
|
321
|
+
// Render name input mode
|
|
322
|
+
if (mode === "add-name") {
|
|
323
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Add New LLM Provider" }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Name: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleNameSubmit, focus: isFocused, placeholder: "e.g., claude, gpt4, local" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Common names: anthropic, openai, ollama, claude, gpt4, local" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next | Esc: cancel" }) })] }));
|
|
324
|
+
}
|
|
325
|
+
// Render model input mode
|
|
326
|
+
if (mode === "edit-model" && editingProvider) {
|
|
327
|
+
const suggestions = MODEL_SUGGESTIONS[editingProvider.config.type] || [];
|
|
328
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Enter Model Name" }), _jsxs(Text, { dimColor: true, children: [" for ", editingProvider.config.type] })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Model: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleModelSubmit, focus: isFocused })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Suggested models:" }), suggestions.slice(0, 4).map((model) => (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: model }) }, model)))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next | Esc: cancel" }) })] }));
|
|
329
|
+
}
|
|
330
|
+
// Render API key input mode
|
|
331
|
+
if (mode === "edit-apikey" && editingProvider) {
|
|
332
|
+
const envVar = editingProvider.config.type === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
333
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Enter API Key" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Leave empty to use ", envVar, " environment variable"] }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "API Key: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleApiKeySubmit, focus: isFocused, mask: "*" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next | Esc: cancel" }) })] }));
|
|
334
|
+
}
|
|
335
|
+
// Render base URL input mode
|
|
336
|
+
if (mode === "edit-baseurl" && editingProvider) {
|
|
337
|
+
const defaultUrl = editingProvider.config.type === "ollama" ? "http://localhost:11434" : "";
|
|
338
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Enter Base URL" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), editingProvider.config.type === "ollama" && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Default: ", defaultUrl] }) })), editingProvider.config.type === "openai" && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Use custom URL for OpenAI-compatible APIs" }) })), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Base URL: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleBaseUrlSubmit, focus: isFocused, placeholder: defaultUrl || "Leave empty for default" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save provider | Esc: cancel" }) })] }));
|
|
339
|
+
}
|
|
340
|
+
// Render edit-provider mode (viewing/editing a single provider)
|
|
341
|
+
if (mode === "edit-provider" && editingProvider) {
|
|
342
|
+
const config = editingProvider.config;
|
|
343
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: ["Edit Provider: ", editingProvider.name] }) }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[T] Type: " }), _jsx(Text, { children: config.type })] }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[M] Model: " }), _jsx(Text, { children: config.model })] }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[A] API Key: " }), _jsx(Text, { dimColor: true, children: config.apiKey ? "********" : "(uses env var)" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[B] Base URL: " }), _jsx(Text, { dimColor: true, children: config.baseUrl || "(default)" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "T/M/A/B: edit field | S: save | Esc: cancel" }) })] }));
|
|
344
|
+
}
|
|
345
|
+
// Calculate scroll indicators
|
|
346
|
+
const canScrollUp = scrollOffset > 0;
|
|
347
|
+
const canScrollDown = scrollOffset + visibleCount < providerNames.length;
|
|
348
|
+
const hasOverflow = providerNames.length > visibleCount;
|
|
349
|
+
// Render list mode
|
|
350
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: label }), _jsxs(Text, { dimColor: true, children: [" (", providerNames.length, " providers)"] })] }), hasOverflow && (_jsx(Box, { children: _jsx(Text, { color: canScrollUp ? "cyan" : "gray", dimColor: !canScrollUp, children: canScrollUp ? " ▲ more" : "" }) })), providerNames.length === 0 ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, italic: true, children: "No LLM providers configured" }) })) : (visibleProviders.map((name) => {
|
|
351
|
+
const actualIndex = providerNames.indexOf(name);
|
|
352
|
+
const isHighlighted = actualIndex === highlightedIndex;
|
|
353
|
+
const config = editProviders[name];
|
|
354
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? "cyan" : undefined, children: isHighlighted ? "▸ " : " " }), _jsx(Text, { bold: isHighlighted, color: isHighlighted ? "cyan" : "yellow", inverse: isHighlighted, children: name }), _jsx(Text, { dimColor: true, children: ": " }), _jsx(Text, { color: "magenta", children: config.type }), _jsx(Text, { dimColor: true, children: " / " }), _jsx(Text, { children: config.model })] }) }, name));
|
|
355
|
+
})), hasOverflow && (_jsx(Box, { children: _jsx(Text, { color: canScrollDown ? "cyan" : "gray", dimColor: !canScrollDown, children: canScrollDown ? " ▼ more" : "" }) })), _jsxs(Box, { children: [_jsx(Text, { color: highlightedIndex === providerNames.length ? "green" : undefined, children: highlightedIndex === providerNames.length ? "▸ " : " " }), _jsx(Text, { bold: highlightedIndex === providerNames.length, color: highlightedIndex === providerNames.length ? "green" : "gray", inverse: highlightedIndex === providerNames.length, children: "+ Add provider" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "j/k: navigate | Enter/e: edit | d: delete" }), _jsx(Text, { dimColor: true, children: "s: save all | Esc: cancel" })] })] }));
|
|
356
|
+
}
|
|
357
|
+
export default LLMProvidersEditor;
|
|
@@ -209,7 +209,7 @@ export function ObjectEditor({ label, entries, onConfirm, onCancel, isFocused =
|
|
|
209
209
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: mode === "add-value" ? "Add New Entry - Enter Value" : `Edit Value for "${newKey}"` }) }), mode === "add-value" && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Key: " }), _jsx(Text, { color: "yellow", children: newKey })] })), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Value: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleValueSubmit, focus: isFocused })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: confirm | Esc: cancel" }) })] }));
|
|
210
210
|
}
|
|
211
211
|
// Render list mode
|
|
212
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Edit: ", label] }), _jsxs(Text, { dimColor: true, children: [" (", keys.length, " entries)"] }), hasOverflow && (_jsxs(Text, { dimColor: true, children: [" [", highlightedIndex + 1, "/", totalOptions, "]"] }))] }), hasOverflow && (_jsx(Box, { children: _jsx(Text, { color: canScrollUp ? "cyan" : "gray", dimColor: !canScrollUp, children: canScrollUp ? " ▲ more" : "" }) })), keys.length === 0 && scrollOffset === 0 ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, italic: true, children: "No entries" }) })) : (visibleItems.map((key, visibleIndex) => {
|
|
212
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Edit: ", label] }), _jsxs(Text, { dimColor: true, children: [" (", keys.length, " entries)"] }), hasOverflow && (_jsxs(Text, { dimColor: true, children: [" ", "[", highlightedIndex + 1, "/", totalOptions, "]"] }))] }), hasOverflow && (_jsx(Box, { children: _jsx(Text, { color: canScrollUp ? "cyan" : "gray", dimColor: !canScrollUp, children: canScrollUp ? " ▲ more" : "" }) })), keys.length === 0 && scrollOffset === 0 ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, italic: true, children: "No entries" }) })) : (visibleItems.map((key, visibleIndex) => {
|
|
213
213
|
// Check if this is the "Add entry" option
|
|
214
214
|
if (key === "__add_entry__") {
|
|
215
215
|
const actualIndex = keys.length;
|
|
@@ -185,6 +185,6 @@ export function Preview({ config, selectedSection, visible = true, maxHeight = 2
|
|
|
185
185
|
if (!config || !sectionData || Object.keys(sectionData).length === 0) {
|
|
186
186
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, width: 40, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "yellow", children: "JSON Preview" }) }), _jsx(Text, { dimColor: true, children: "No data to preview" })] }));
|
|
187
187
|
}
|
|
188
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, width: 40, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "yellow", children: "JSON Preview" }), _jsx(Text, { dimColor: true, children: currentSection?.label || selectedSection })] }), _jsxs(Box, { flexDirection: "column", children: [displayLines.map((line, index) => (_jsx(HighlightedLineComponent, { line: line }, index))), isOverflowing && (_jsxs(Text, { dimColor: true, children: ["
|
|
188
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, width: 40, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "yellow", children: "JSON Preview" }), _jsx(Text, { dimColor: true, children: currentSection?.label || selectedSection })] }), _jsxs(Box, { flexDirection: "column", children: [displayLines.map((line, index) => (_jsx(HighlightedLineComponent, { line: line }, index))), isOverflowing && (_jsxs(Text, { dimColor: true, children: [" ... (", highlightedLines.length - maxHeight + 1, " more lines)"] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[Tab] to hide" }) })] }));
|
|
189
189
|
}
|
|
190
190
|
export default Preview;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { RespondersConfig } from "../../utils/config.js";
|
|
3
|
+
export interface RespondersEditorProps {
|
|
4
|
+
/** The label to display for this field */
|
|
5
|
+
label: string;
|
|
6
|
+
/** The current responders config */
|
|
7
|
+
responders: RespondersConfig;
|
|
8
|
+
/** Called when the user confirms the edit */
|
|
9
|
+
onConfirm: (newResponders: RespondersConfig) => void;
|
|
10
|
+
/** Called when the user cancels the edit (Esc) */
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
/** Whether this editor has focus */
|
|
13
|
+
isFocused?: boolean;
|
|
14
|
+
/** Maximum height for the list (for scrolling) */
|
|
15
|
+
maxHeight?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* RespondersEditor component for editing chat responder configurations.
|
|
19
|
+
* Provides a user-friendly interface for adding, editing, and removing responders.
|
|
20
|
+
*/
|
|
21
|
+
export declare function RespondersEditor({ label, responders, onConfirm, onCancel, isFocused, maxHeight, }: RespondersEditorProps): React.ReactElement;
|
|
22
|
+
export default RespondersEditor;
|