swiftroutercli 4.0.3 → 4.0.4
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 +1 -1
- package/dist/ui/Chat.js +45 -54
- package/package.json +2 -1
- package/src/index.ts +1 -1
- package/src/ui/Chat.tsx +59 -55
package/dist/index.js
CHANGED
|
@@ -71,7 +71,7 @@ async function resolveModel(config) {
|
|
|
71
71
|
program
|
|
72
72
|
.name("swiftroutercli")
|
|
73
73
|
.description("CLI for SwiftRouter AI Gateway")
|
|
74
|
-
.version("4.0.
|
|
74
|
+
.version("4.0.4");
|
|
75
75
|
program
|
|
76
76
|
.command("config")
|
|
77
77
|
.description("Manually configure the CLI with your SwiftRouter API Key and Base URL")
|
package/dist/ui/Chat.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { render, Box, Text, useInput, Static } from "ink";
|
|
3
|
+
import TextInput from "ink-text-input";
|
|
3
4
|
import { streamChatCompletion, fetchModels } from "../api/client.js";
|
|
4
5
|
import chalk from "chalk";
|
|
5
6
|
import { loadHistory, saveHistory } from "../config.js";
|
|
@@ -12,11 +13,10 @@ marked.setOptions({
|
|
|
12
13
|
renderer: new TerminalRenderer()
|
|
13
14
|
});
|
|
14
15
|
const Chat = ({ config, model, initialPrompt, approvalMode }) => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
]);
|
|
16
|
+
const hasInitialPrompt = initialPrompt.trim().length > 0;
|
|
17
|
+
const [messages, setMessages] = useState(hasInitialPrompt ? [{ id: 0, role: "user", content: initialPrompt }] : []);
|
|
18
18
|
const [streamingContent, setStreamingContent] = useState("");
|
|
19
|
-
const [isStreaming, setIsStreaming] = useState(
|
|
19
|
+
const [isStreaming, setIsStreaming] = useState(hasInitialPrompt);
|
|
20
20
|
const [error, setError] = useState(null);
|
|
21
21
|
const [input, setInput] = useState("");
|
|
22
22
|
const [history, setHistory] = useState([]);
|
|
@@ -56,7 +56,6 @@ const Chat = ({ config, model, initialPrompt, approvalMode }) => {
|
|
|
56
56
|
{ id: prev.length, role: "system", content: `Sandbox Output:\n${output || "Done"}` }
|
|
57
57
|
]);
|
|
58
58
|
setIsExecuting(false);
|
|
59
|
-
// Recursive automation could go here in V5
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
else {
|
|
@@ -70,6 +69,7 @@ const Chat = ({ config, model, initialPrompt, approvalMode }) => {
|
|
|
70
69
|
setIsStreaming(false);
|
|
71
70
|
});
|
|
72
71
|
}, [messages, isStreaming, config, activeModel]);
|
|
72
|
+
// Handle command confirmation and history navigation
|
|
73
73
|
useInput((char, key) => {
|
|
74
74
|
if (isSelectingModel)
|
|
75
75
|
return;
|
|
@@ -93,44 +93,8 @@ const Chat = ({ config, model, initialPrompt, approvalMode }) => {
|
|
|
93
93
|
}
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
99
|
-
process.exit(0);
|
|
100
|
-
}
|
|
101
|
-
if (trimmed === "/clear") {
|
|
102
|
-
setMessages([]);
|
|
103
|
-
setInput("");
|
|
104
|
-
setHistoryIndex(-1);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (trimmed === "/models") {
|
|
108
|
-
setIsSelectingModel(true);
|
|
109
|
-
setIsLoadingModels(true);
|
|
110
|
-
setInput("");
|
|
111
|
-
fetchModels(config).then(models => {
|
|
112
|
-
const options = models.map(m => ({ label: m.id || m.name, value: m.id || m.name }));
|
|
113
|
-
setModelOptions(options.length ? options : [{ label: "mock-model", value: "mock-model" }]);
|
|
114
|
-
setIsLoadingModels(false);
|
|
115
|
-
}).catch(err => {
|
|
116
|
-
setError(err);
|
|
117
|
-
setIsSelectingModel(false);
|
|
118
|
-
setIsLoadingModels(false);
|
|
119
|
-
});
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (trimmed.length > 0) {
|
|
123
|
-
const newPrompt = trimmed;
|
|
124
|
-
setMessages((prev) => [...prev, { id: prev.length, role: "user", content: newPrompt }]);
|
|
125
|
-
const newHistory = [...history, newPrompt];
|
|
126
|
-
setHistory(newHistory);
|
|
127
|
-
saveHistory(newHistory);
|
|
128
|
-
setInput("");
|
|
129
|
-
setHistoryIndex(-1);
|
|
130
|
-
setIsStreaming(true);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else if (key.upArrow) {
|
|
96
|
+
// History navigation with Up/Down arrows
|
|
97
|
+
if (key.upArrow) {
|
|
134
98
|
if (history.length > 0 && historyIndex < history.length - 1) {
|
|
135
99
|
const newIdx = historyIndex + 1;
|
|
136
100
|
setHistoryIndex(newIdx);
|
|
@@ -148,13 +112,43 @@ const Chat = ({ config, model, initialPrompt, approvalMode }) => {
|
|
|
148
112
|
setInput("");
|
|
149
113
|
}
|
|
150
114
|
}
|
|
151
|
-
|
|
152
|
-
|
|
115
|
+
});
|
|
116
|
+
const handleSubmit = (value) => {
|
|
117
|
+
const trimmed = value.trim();
|
|
118
|
+
if (!trimmed)
|
|
119
|
+
return;
|
|
120
|
+
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
121
|
+
process.exit(0);
|
|
153
122
|
}
|
|
154
|
-
|
|
155
|
-
|
|
123
|
+
if (trimmed === "/clear") {
|
|
124
|
+
setMessages([]);
|
|
125
|
+
setInput("");
|
|
126
|
+
setHistoryIndex(-1);
|
|
127
|
+
return;
|
|
156
128
|
}
|
|
157
|
-
|
|
129
|
+
if (trimmed === "/models") {
|
|
130
|
+
setIsSelectingModel(true);
|
|
131
|
+
setIsLoadingModels(true);
|
|
132
|
+
setInput("");
|
|
133
|
+
fetchModels(config).then(models => {
|
|
134
|
+
const options = models.map(m => ({ label: m.id || m.name, value: m.id || m.name }));
|
|
135
|
+
setModelOptions(options.length ? options : [{ label: "mock-model", value: "mock-model" }]);
|
|
136
|
+
setIsLoadingModels(false);
|
|
137
|
+
}).catch(err => {
|
|
138
|
+
setError(err);
|
|
139
|
+
setIsSelectingModel(false);
|
|
140
|
+
setIsLoadingModels(false);
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
setMessages((prev) => [...prev, { id: prev.length, role: "user", content: trimmed }]);
|
|
145
|
+
const newHistory = [...history, trimmed];
|
|
146
|
+
setHistory(newHistory);
|
|
147
|
+
saveHistory(newHistory);
|
|
148
|
+
setInput("");
|
|
149
|
+
setHistoryIndex(-1);
|
|
150
|
+
setIsStreaming(true);
|
|
151
|
+
};
|
|
158
152
|
return (React.createElement(React.Fragment, null,
|
|
159
153
|
React.createElement(Static, { items: messages }, msg => (React.createElement(Box, { key: msg.id, flexDirection: "column", marginBottom: 1 },
|
|
160
154
|
React.createElement(Text, { bold: true, color: msg.role === "user" ? "blue" : "green" },
|
|
@@ -175,10 +169,9 @@ const Chat = ({ config, model, initialPrompt, approvalMode }) => {
|
|
|
175
169
|
setActiveModel(item.value);
|
|
176
170
|
setIsSelectingModel(false);
|
|
177
171
|
} })))) : (React.createElement(React.Fragment, null,
|
|
178
|
-
!isStreaming && !pendingCommand && !isExecuting && (React.createElement(Box, {
|
|
179
|
-
React.createElement(Text, { bold: true, color: "blue" }, "
|
|
180
|
-
React.createElement(
|
|
181
|
-
React.createElement(Text, { dimColor: true }, " \u2588"))),
|
|
172
|
+
!isStreaming && !pendingCommand && !isExecuting && (React.createElement(Box, { borderStyle: "round", borderColor: "blue", paddingX: 1 },
|
|
173
|
+
React.createElement(Text, { bold: true, color: "blue" }, "\u276F "),
|
|
174
|
+
React.createElement(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: "Type a message... (/models, /clear, /exit)" }))),
|
|
182
175
|
pendingCommand && !isExecuting && (React.createElement(Box, { flexDirection: "row", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginTop: 1 },
|
|
183
176
|
React.createElement(Text, { color: "yellow" }, "Execute suggested command? "),
|
|
184
177
|
React.createElement(Text, { bold: true }, pendingCommand),
|
|
@@ -215,8 +208,6 @@ export function startChat(config, model, prompt, approvalMode = "suggest", quiet
|
|
|
215
208
|
fullResponse += text;
|
|
216
209
|
}, () => {
|
|
217
210
|
console.log("\n");
|
|
218
|
-
// If full-auto is requested in quiet mode, we would parse bash blocks and execute them here.
|
|
219
|
-
// For now, headless simply streams the raw text response for CI pipelines.
|
|
220
211
|
if (approvalMode === "full-auto") {
|
|
221
212
|
const bashMatch = fullResponse.match(/```(?:bash|sh)\n([\s\S]*?)\n```/);
|
|
222
213
|
if (bashMatch) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swiftroutercli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "The official SwiftRouter Command Line Interface using React Ink Components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"eventsource-parser": "^3.0.6",
|
|
25
25
|
"ink": "^6.8.0",
|
|
26
26
|
"ink-select-input": "^6.2.0",
|
|
27
|
+
"ink-text-input": "^6.0.0",
|
|
27
28
|
"marked": "^15.0.12",
|
|
28
29
|
"marked-terminal": "^7.3.0",
|
|
29
30
|
"ora": "^5.4.1",
|
package/src/index.ts
CHANGED
package/src/ui/Chat.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { render, Box, Text, useInput, Static } from "ink";
|
|
3
|
+
import TextInput from "ink-text-input";
|
|
3
4
|
import { streamChatCompletion, fetchModels } from "../api/client.js";
|
|
4
5
|
import chalk from "chalk";
|
|
5
6
|
import { Config, loadHistory, saveHistory } from "../config.js";
|
|
@@ -26,11 +27,13 @@ interface ChatProps {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt, approvalMode }) => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
]
|
|
30
|
+
const hasInitialPrompt = initialPrompt.trim().length > 0;
|
|
31
|
+
|
|
32
|
+
const [messages, setMessages] = useState<{ id: number; role: string; content: string }[]>(
|
|
33
|
+
hasInitialPrompt ? [{ id: 0, role: "user", content: initialPrompt }] : []
|
|
34
|
+
);
|
|
32
35
|
const [streamingContent, setStreamingContent] = useState("");
|
|
33
|
-
const [isStreaming, setIsStreaming] = useState(
|
|
36
|
+
const [isStreaming, setIsStreaming] = useState(hasInitialPrompt);
|
|
34
37
|
const [error, setError] = useState<Error | null>(null);
|
|
35
38
|
const [input, setInput] = useState("");
|
|
36
39
|
const [history, setHistory] = useState<string[]>([]);
|
|
@@ -82,7 +85,6 @@ const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt, approvalMode
|
|
|
82
85
|
{ id: prev.length, role: "system", content: `Sandbox Output:\n${output || "Done"}` }
|
|
83
86
|
]);
|
|
84
87
|
setIsExecuting(false);
|
|
85
|
-
// Recursive automation could go here in V5
|
|
86
88
|
});
|
|
87
89
|
} else {
|
|
88
90
|
setPendingCommand(command);
|
|
@@ -99,6 +101,7 @@ const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt, approvalMode
|
|
|
99
101
|
);
|
|
100
102
|
}, [messages, isStreaming, config, activeModel]);
|
|
101
103
|
|
|
104
|
+
// Handle command confirmation and history navigation
|
|
102
105
|
useInput((char: string, key: any) => {
|
|
103
106
|
if (isSelectingModel) return;
|
|
104
107
|
if (isStreaming) return;
|
|
@@ -121,46 +124,8 @@ const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt, approvalMode
|
|
|
121
124
|
return;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
127
|
-
process.exit(0);
|
|
128
|
-
}
|
|
129
|
-
if (trimmed === "/clear") {
|
|
130
|
-
setMessages([]);
|
|
131
|
-
setInput("");
|
|
132
|
-
setHistoryIndex(-1);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (trimmed === "/models") {
|
|
136
|
-
setIsSelectingModel(true);
|
|
137
|
-
setIsLoadingModels(true);
|
|
138
|
-
setInput("");
|
|
139
|
-
fetchModels(config).then(models => {
|
|
140
|
-
const options = models.map(m => ({ label: m.id || m.name, value: m.id || m.name }));
|
|
141
|
-
setModelOptions(options.length ? options : [{ label: "mock-model", value: "mock-model" }]);
|
|
142
|
-
setIsLoadingModels(false);
|
|
143
|
-
}).catch(err => {
|
|
144
|
-
setError(err);
|
|
145
|
-
setIsSelectingModel(false);
|
|
146
|
-
setIsLoadingModels(false);
|
|
147
|
-
});
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (trimmed.length > 0) {
|
|
152
|
-
const newPrompt = trimmed;
|
|
153
|
-
setMessages((prev) => [...prev, { id: prev.length, role: "user", content: newPrompt }]);
|
|
154
|
-
|
|
155
|
-
const newHistory = [...history, newPrompt];
|
|
156
|
-
setHistory(newHistory);
|
|
157
|
-
saveHistory(newHistory);
|
|
158
|
-
|
|
159
|
-
setInput("");
|
|
160
|
-
setHistoryIndex(-1);
|
|
161
|
-
setIsStreaming(true);
|
|
162
|
-
}
|
|
163
|
-
} else if (key.upArrow) {
|
|
127
|
+
// History navigation with Up/Down arrows
|
|
128
|
+
if (key.upArrow) {
|
|
164
129
|
if (history.length > 0 && historyIndex < history.length - 1) {
|
|
165
130
|
const newIdx = historyIndex + 1;
|
|
166
131
|
setHistoryIndex(newIdx);
|
|
@@ -175,13 +140,49 @@ const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt, approvalMode
|
|
|
175
140
|
setHistoryIndex(-1);
|
|
176
141
|
setInput("");
|
|
177
142
|
}
|
|
178
|
-
} else if (key.backspace || key.delete) {
|
|
179
|
-
setInput((prev) => prev.slice(0, -1));
|
|
180
|
-
} else if (char) {
|
|
181
|
-
setInput((prev) => prev + char);
|
|
182
143
|
}
|
|
183
144
|
});
|
|
184
145
|
|
|
146
|
+
const handleSubmit = (value: string) => {
|
|
147
|
+
const trimmed = value.trim();
|
|
148
|
+
if (!trimmed) return;
|
|
149
|
+
|
|
150
|
+
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
if (trimmed === "/clear") {
|
|
154
|
+
setMessages([]);
|
|
155
|
+
setInput("");
|
|
156
|
+
setHistoryIndex(-1);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (trimmed === "/models") {
|
|
160
|
+
setIsSelectingModel(true);
|
|
161
|
+
setIsLoadingModels(true);
|
|
162
|
+
setInput("");
|
|
163
|
+
fetchModels(config).then(models => {
|
|
164
|
+
const options = models.map(m => ({ label: m.id || m.name, value: m.id || m.name }));
|
|
165
|
+
setModelOptions(options.length ? options : [{ label: "mock-model", value: "mock-model" }]);
|
|
166
|
+
setIsLoadingModels(false);
|
|
167
|
+
}).catch(err => {
|
|
168
|
+
setError(err);
|
|
169
|
+
setIsSelectingModel(false);
|
|
170
|
+
setIsLoadingModels(false);
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
setMessages((prev) => [...prev, { id: prev.length, role: "user", content: trimmed }]);
|
|
176
|
+
|
|
177
|
+
const newHistory = [...history, trimmed];
|
|
178
|
+
setHistory(newHistory);
|
|
179
|
+
saveHistory(newHistory);
|
|
180
|
+
|
|
181
|
+
setInput("");
|
|
182
|
+
setHistoryIndex(-1);
|
|
183
|
+
setIsStreaming(true);
|
|
184
|
+
};
|
|
185
|
+
|
|
185
186
|
return (
|
|
186
187
|
<>
|
|
187
188
|
<Static items={messages}>
|
|
@@ -224,10 +225,15 @@ const Chat: React.FC<ChatProps> = ({ config, model, initialPrompt, approvalMode
|
|
|
224
225
|
) : (
|
|
225
226
|
<>
|
|
226
227
|
{!isStreaming && !pendingCommand && !isExecuting && (
|
|
227
|
-
<Box
|
|
228
|
-
<Text bold color="blue"
|
|
229
|
-
|
|
230
|
-
<
|
|
228
|
+
<Box borderStyle="round" borderColor="blue" paddingX={1}>
|
|
229
|
+
<Text bold color="blue">❯ </Text>
|
|
230
|
+
{/* @ts-ignore */}
|
|
231
|
+
<TextInput
|
|
232
|
+
value={input}
|
|
233
|
+
onChange={setInput}
|
|
234
|
+
onSubmit={handleSubmit}
|
|
235
|
+
placeholder="Type a message... (/models, /clear, /exit)"
|
|
236
|
+
/>
|
|
231
237
|
</Box>
|
|
232
238
|
)}
|
|
233
239
|
|
|
@@ -293,8 +299,6 @@ export function startChat(config: Config, model: string, prompt: string, approva
|
|
|
293
299
|
() => {
|
|
294
300
|
console.log("\n");
|
|
295
301
|
|
|
296
|
-
// If full-auto is requested in quiet mode, we would parse bash blocks and execute them here.
|
|
297
|
-
// For now, headless simply streams the raw text response for CI pipelines.
|
|
298
302
|
if (approvalMode === "full-auto") {
|
|
299
303
|
const bashMatch = fullResponse.match(/```(?:bash|sh)\n([\s\S]*?)\n```/);
|
|
300
304
|
if (bashMatch) {
|