@novaqore/atom 0.1.1 → 0.1.3
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/index.js +3 -2
- package/package.json +1 -1
- package/src/app.js +5 -1
- package/src/components/Chat/AssistantMessage.js +26 -0
- package/src/components/Chat/RunningIndicator.js +18 -0
- package/src/components/Chat/ToolCall.js +32 -0
- package/src/components/Chat/ToolResult.js +50 -0
- package/src/components/Chat/UserMessage.js +16 -0
- package/src/screens/chat.js +64 -167
- package/src/setup.js +0 -3
package/index.js
CHANGED
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -60,7 +60,11 @@ export function App({ version, unhinged }) {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
if (screen === "chat") {
|
|
63
|
-
return React.createElement(ChatScreen, {
|
|
63
|
+
return React.createElement(ChatScreen, {
|
|
64
|
+
version,
|
|
65
|
+
unhinged,
|
|
66
|
+
onBack: () => setScreen("main-menu"),
|
|
67
|
+
});
|
|
64
68
|
}
|
|
65
69
|
if (screen === "system") {
|
|
66
70
|
return React.createElement(SystemScreen, {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { renderMarkdown } from "../../utils/markdown.js";
|
|
4
|
+
|
|
5
|
+
export const AssistantMessage = React.memo(function AssistantMessage({
|
|
6
|
+
content,
|
|
7
|
+
streaming = false,
|
|
8
|
+
}) {
|
|
9
|
+
const rendered = renderMarkdown(content, { streaming });
|
|
10
|
+
const nlIdx = rendered.indexOf("\n");
|
|
11
|
+
const firstLine = nlIdx === -1 ? rendered : rendered.slice(0, nlIdx);
|
|
12
|
+
const rest = nlIdx === -1 ? "" : rendered.slice(nlIdx + 1);
|
|
13
|
+
|
|
14
|
+
return React.createElement(
|
|
15
|
+
Box,
|
|
16
|
+
{ marginBottom: streaming ? 0 : 1, flexDirection: "column" },
|
|
17
|
+
React.createElement(
|
|
18
|
+
Text,
|
|
19
|
+
null,
|
|
20
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Atom:"),
|
|
21
|
+
" ",
|
|
22
|
+
firstLine
|
|
23
|
+
),
|
|
24
|
+
rest ? React.createElement(Text, null, rest) : null
|
|
25
|
+
);
|
|
26
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
function formatMs(ms) {
|
|
5
|
+
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function RunningIndicator({ elapsed }) {
|
|
9
|
+
return React.createElement(
|
|
10
|
+
Box,
|
|
11
|
+
{ paddingLeft: 2, marginBottom: 1 },
|
|
12
|
+
React.createElement(
|
|
13
|
+
Text,
|
|
14
|
+
{ dimColor: true },
|
|
15
|
+
`⎿ running ${formatMs(elapsed)}`
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
|
|
4
|
+
function formatName(name) {
|
|
5
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function formatArgs(rawArgs) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(rawArgs || "{}");
|
|
11
|
+
const keys = Object.keys(parsed);
|
|
12
|
+
if (keys.length === 1) return String(parsed[keys[0]]);
|
|
13
|
+
return keys.map((k) => `${k}=${JSON.stringify(parsed[k])}`).join(", ");
|
|
14
|
+
} catch {
|
|
15
|
+
return rawArgs || "";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const ToolCall = React.memo(function ToolCall({ tc }) {
|
|
20
|
+
return React.createElement(
|
|
21
|
+
Text,
|
|
22
|
+
null,
|
|
23
|
+
React.createElement(
|
|
24
|
+
Text,
|
|
25
|
+
{ bold: true, color: "yellow" },
|
|
26
|
+
formatName(tc.function.name)
|
|
27
|
+
),
|
|
28
|
+
React.createElement(Text, { dimColor: true }, "("),
|
|
29
|
+
formatArgs(tc.function.arguments),
|
|
30
|
+
React.createElement(Text, { dimColor: true }, ")")
|
|
31
|
+
);
|
|
32
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
const MAX_OUTPUT_LINES = 2;
|
|
5
|
+
|
|
6
|
+
function formatMs(ms) {
|
|
7
|
+
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ToolResult = React.memo(function ToolResult({ msg, duration }) {
|
|
11
|
+
const content = (msg.content || "").trimEnd();
|
|
12
|
+
const dur = duration ? ` · ${formatMs(duration)}` : "";
|
|
13
|
+
|
|
14
|
+
if (!content) {
|
|
15
|
+
return React.createElement(
|
|
16
|
+
Box,
|
|
17
|
+
{ paddingLeft: 2, marginBottom: 1 },
|
|
18
|
+
React.createElement(Text, { dimColor: true }, `⎿ (No output)${dur}`)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const lines = content.split("\n");
|
|
23
|
+
const visible = lines.slice(0, MAX_OUTPUT_LINES);
|
|
24
|
+
const overflow = lines.length - MAX_OUTPUT_LINES;
|
|
25
|
+
|
|
26
|
+
const children = visible.map((line, idx) => {
|
|
27
|
+
const isLastShown = idx === visible.length - 1 && overflow === 0;
|
|
28
|
+
const suffix = isLastShown ? dur : "";
|
|
29
|
+
return React.createElement(
|
|
30
|
+
Text,
|
|
31
|
+
{ key: `line-${idx}`, dimColor: true },
|
|
32
|
+
idx === 0 ? `⎿ ${line}${suffix}` : ` ${line}${suffix}`
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
if (overflow > 0) {
|
|
36
|
+
children.push(
|
|
37
|
+
React.createElement(
|
|
38
|
+
Text,
|
|
39
|
+
{ key: "overflow", dimColor: true },
|
|
40
|
+
` … +${overflow} lines${dur}`
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return React.createElement(
|
|
46
|
+
Box,
|
|
47
|
+
{ flexDirection: "column", paddingLeft: 2, marginBottom: 1 },
|
|
48
|
+
children
|
|
49
|
+
);
|
|
50
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
export const UserMessage = React.memo(function UserMessage({ content }) {
|
|
5
|
+
return React.createElement(
|
|
6
|
+
Box,
|
|
7
|
+
{ marginBottom: 1 },
|
|
8
|
+
React.createElement(
|
|
9
|
+
Text,
|
|
10
|
+
null,
|
|
11
|
+
React.createElement(Text, { bold: true, color: "white" }, "You:"),
|
|
12
|
+
" ",
|
|
13
|
+
React.createElement(Text, { dimColor: true }, content)
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
});
|
package/src/screens/chat.js
CHANGED
|
@@ -6,10 +6,14 @@ import { tools, executeTool } from "../tools/index.js";
|
|
|
6
6
|
import { buildSystemPrompt } from "../utils/system-prompt.js";
|
|
7
7
|
import { ChatInput } from "../components/Chat/ChatInput.js";
|
|
8
8
|
import { ConfirmDangerousCommand } from "../components/Chat/ConfirmDangerousCommand.js";
|
|
9
|
+
import { UserMessage } from "../components/Chat/UserMessage.js";
|
|
10
|
+
import { AssistantMessage } from "../components/Chat/AssistantMessage.js";
|
|
11
|
+
import { ToolCall } from "../components/Chat/ToolCall.js";
|
|
12
|
+
import { ToolResult } from "../components/Chat/ToolResult.js";
|
|
13
|
+
import { RunningIndicator } from "../components/Chat/RunningIndicator.js";
|
|
9
14
|
import { Header } from "../components/Header.js";
|
|
10
|
-
import { renderMarkdown } from "../utils/markdown.js";
|
|
11
15
|
|
|
12
|
-
export function ChatScreen({ version, unhinged }) {
|
|
16
|
+
export function ChatScreen({ version, unhinged, onBack }) {
|
|
13
17
|
const [nq] = useState(() => new NovaQoreAI(SERVICE_FILE));
|
|
14
18
|
const [messages, setMessages] = useState([]);
|
|
15
19
|
const [input, setInput] = useState("");
|
|
@@ -23,6 +27,7 @@ export function ChatScreen({ version, unhinged }) {
|
|
|
23
27
|
const [running, setRunning] = useState(null);
|
|
24
28
|
const [durations, setDurations] = useState({});
|
|
25
29
|
const stopRef = useRef(null);
|
|
30
|
+
const abortedRef = useRef(false);
|
|
26
31
|
|
|
27
32
|
const toApiMessage = (m) => {
|
|
28
33
|
if (m.role === "tool") {
|
|
@@ -44,11 +49,15 @@ export function ChatScreen({ version, unhinged }) {
|
|
|
44
49
|
}, [running?.id]);
|
|
45
50
|
|
|
46
51
|
useInput((_input, key) => {
|
|
47
|
-
if (key.escape
|
|
52
|
+
if (!key.escape) return;
|
|
53
|
+
if (sending && stopRef.current) {
|
|
54
|
+
abortedRef.current = true;
|
|
48
55
|
const stop = stopRef.current;
|
|
49
56
|
stopRef.current = null;
|
|
50
57
|
stop();
|
|
58
|
+
return;
|
|
51
59
|
}
|
|
60
|
+
if (!sending && onBack) onBack();
|
|
52
61
|
});
|
|
53
62
|
|
|
54
63
|
const askConfirm = (command) =>
|
|
@@ -63,9 +72,6 @@ export function ChatScreen({ version, unhinged }) {
|
|
|
63
72
|
setSending(true);
|
|
64
73
|
setError(null);
|
|
65
74
|
|
|
66
|
-
let acc = "";
|
|
67
|
-
let toolCalls = [];
|
|
68
|
-
|
|
69
75
|
try {
|
|
70
76
|
while (true) {
|
|
71
77
|
setStreaming("");
|
|
@@ -80,8 +86,8 @@ export function ChatScreen({ version, unhinged }) {
|
|
|
80
86
|
);
|
|
81
87
|
stopRef.current = stop;
|
|
82
88
|
|
|
83
|
-
acc = "";
|
|
84
|
-
toolCalls = [];
|
|
89
|
+
let acc = "";
|
|
90
|
+
const toolCalls = [];
|
|
85
91
|
for await (const chunk of stream) {
|
|
86
92
|
if (chunk.timings) {
|
|
87
93
|
const t = chunk.timings;
|
|
@@ -122,12 +128,23 @@ export function ChatScreen({ version, unhinged }) {
|
|
|
122
128
|
}
|
|
123
129
|
}
|
|
124
130
|
|
|
131
|
+
setStreaming("");
|
|
132
|
+
setStreamingTools([]);
|
|
133
|
+
|
|
134
|
+
if (abortedRef.current) {
|
|
135
|
+
abortedRef.current = false;
|
|
136
|
+
if (acc.trim()) {
|
|
137
|
+
const partial = { role: "assistant", content: acc };
|
|
138
|
+
convo = [...convo, partial];
|
|
139
|
+
setMessages(convo);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
const assistantMsg = { role: "assistant", content: acc };
|
|
126
145
|
if (toolCalls.length > 0) assistantMsg.tool_calls = toolCalls;
|
|
127
146
|
convo = [...convo, assistantMsg];
|
|
128
147
|
setMessages(convo);
|
|
129
|
-
setStreaming("");
|
|
130
|
-
setStreamingTools([]);
|
|
131
148
|
|
|
132
149
|
if (toolCalls.length === 0) break;
|
|
133
150
|
|
|
@@ -155,107 +172,16 @@ export function ChatScreen({ version, unhinged }) {
|
|
|
155
172
|
}
|
|
156
173
|
setSending(false);
|
|
157
174
|
} catch (err) {
|
|
158
|
-
|
|
159
|
-
err.
|
|
160
|
-
|
|
161
|
-
/aborted/i.test(err.message || "");
|
|
162
|
-
if (aborted) {
|
|
163
|
-
if (acc.trim() || toolCalls.length > 0) {
|
|
164
|
-
const partial = { role: "assistant", content: acc };
|
|
165
|
-
if (toolCalls.length > 0) partial.tool_calls = toolCalls;
|
|
166
|
-
setMessages((prev) => [...prev, partial]);
|
|
167
|
-
}
|
|
168
|
-
setStreaming("");
|
|
169
|
-
setStreamingTools([]);
|
|
170
|
-
} else {
|
|
171
|
-
setError(
|
|
172
|
-
`${err.message} | ${err.cause?.code || ""} ${err.cause?.message || ""}`
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
+
setError(
|
|
176
|
+
`${err.message} | ${err.cause?.code || ""} ${err.cause?.message || ""}`
|
|
177
|
+
);
|
|
175
178
|
setSending(false);
|
|
176
179
|
} finally {
|
|
177
180
|
stopRef.current = null;
|
|
181
|
+
abortedRef.current = false;
|
|
178
182
|
}
|
|
179
183
|
};
|
|
180
184
|
|
|
181
|
-
const formatToolName = (name) =>
|
|
182
|
-
name.charAt(0).toUpperCase() + name.slice(1);
|
|
183
|
-
|
|
184
|
-
const formatToolArgs = (rawArgs) => {
|
|
185
|
-
try {
|
|
186
|
-
const parsed = JSON.parse(rawArgs || "{}");
|
|
187
|
-
const keys = Object.keys(parsed);
|
|
188
|
-
if (keys.length === 1) return String(parsed[keys[0]]);
|
|
189
|
-
return keys.map((k) => `${k}=${JSON.stringify(parsed[k])}`).join(", ");
|
|
190
|
-
} catch {
|
|
191
|
-
return rawArgs || "";
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const renderToolCall = (tc, key) =>
|
|
196
|
-
React.createElement(
|
|
197
|
-
Box,
|
|
198
|
-
{ key },
|
|
199
|
-
React.createElement(
|
|
200
|
-
Text,
|
|
201
|
-
{ bold: true, color: "yellow" },
|
|
202
|
-
formatToolName(tc.function.name)
|
|
203
|
-
),
|
|
204
|
-
React.createElement(Text, { dimColor: true }, "("),
|
|
205
|
-
React.createElement(Text, null, formatToolArgs(tc.function.arguments)),
|
|
206
|
-
React.createElement(Text, { dimColor: true }, ")")
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
const MAX_OUTPUT_LINES = 2;
|
|
210
|
-
|
|
211
|
-
const formatMs = (ms) =>
|
|
212
|
-
ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
213
|
-
|
|
214
|
-
const renderToolResult = (msg, key) => {
|
|
215
|
-
const content = (msg.content || "").trimEnd();
|
|
216
|
-
const ms = durations[msg.tool_call_id];
|
|
217
|
-
const dur = ms ? ` · ${formatMs(ms)}` : "";
|
|
218
|
-
if (!content) {
|
|
219
|
-
return React.createElement(
|
|
220
|
-
Box,
|
|
221
|
-
{ key, paddingLeft: 2, marginBottom: 1 },
|
|
222
|
-
React.createElement(
|
|
223
|
-
Text,
|
|
224
|
-
{ dimColor: true },
|
|
225
|
-
`⎿ (No output)${dur}`
|
|
226
|
-
)
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
const lines = content.split("\n");
|
|
230
|
-
const visible = lines.slice(0, MAX_OUTPUT_LINES);
|
|
231
|
-
const overflow = lines.length - MAX_OUTPUT_LINES;
|
|
232
|
-
|
|
233
|
-
const children = visible.map((line, idx) => {
|
|
234
|
-
const isLastShown = idx === visible.length - 1 && overflow === 0;
|
|
235
|
-
const suffix = isLastShown ? dur : "";
|
|
236
|
-
return React.createElement(
|
|
237
|
-
Text,
|
|
238
|
-
{ key: `line-${idx}`, dimColor: true },
|
|
239
|
-
idx === 0 ? `⎿ ${line}${suffix}` : ` ${line}${suffix}`
|
|
240
|
-
);
|
|
241
|
-
});
|
|
242
|
-
if (overflow > 0) {
|
|
243
|
-
children.push(
|
|
244
|
-
React.createElement(
|
|
245
|
-
Text,
|
|
246
|
-
{ key: "overflow", dimColor: true },
|
|
247
|
-
` … +${overflow} lines${dur}`
|
|
248
|
-
)
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return React.createElement(
|
|
253
|
-
Box,
|
|
254
|
-
{ key, flexDirection: "column", paddingLeft: 2, marginBottom: 1 },
|
|
255
|
-
children
|
|
256
|
-
);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
185
|
return React.createElement(
|
|
260
186
|
Box,
|
|
261
187
|
{ flexDirection: "column" },
|
|
@@ -263,88 +189,59 @@ export function ChatScreen({ version, unhinged }) {
|
|
|
263
189
|
...messages.flatMap((msg, i) => {
|
|
264
190
|
if (msg.role === "user") {
|
|
265
191
|
return [
|
|
266
|
-
React.createElement(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
React.createElement(Text, { dimColor: true }, msg.content)
|
|
271
|
-
),
|
|
192
|
+
React.createElement(UserMessage, {
|
|
193
|
+
key: `m-${i}`,
|
|
194
|
+
content: msg.content,
|
|
195
|
+
}),
|
|
272
196
|
];
|
|
273
197
|
}
|
|
274
198
|
if (msg.role === "assistant") {
|
|
275
199
|
const nodes = [];
|
|
276
200
|
if (msg.content) {
|
|
277
|
-
const rendered = renderMarkdown(msg.content);
|
|
278
|
-
const nlIdx = rendered.indexOf("\n");
|
|
279
|
-
const firstLine = nlIdx === -1 ? rendered : rendered.slice(0, nlIdx);
|
|
280
|
-
const rest = nlIdx === -1 ? "" : rendered.slice(nlIdx + 1);
|
|
281
201
|
nodes.push(
|
|
282
|
-
React.createElement(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
marginBottom: 1,
|
|
287
|
-
flexDirection: "column",
|
|
288
|
-
},
|
|
289
|
-
React.createElement(
|
|
290
|
-
Box,
|
|
291
|
-
null,
|
|
292
|
-
React.createElement(
|
|
293
|
-
Text,
|
|
294
|
-
{ bold: true, color: "cyan" },
|
|
295
|
-
"Atom:"
|
|
296
|
-
),
|
|
297
|
-
React.createElement(Text, null, " ", firstLine)
|
|
298
|
-
),
|
|
299
|
-
rest ? React.createElement(Text, null, rest) : null
|
|
300
|
-
)
|
|
202
|
+
React.createElement(AssistantMessage, {
|
|
203
|
+
key: `m-${i}`,
|
|
204
|
+
content: msg.content,
|
|
205
|
+
})
|
|
301
206
|
);
|
|
302
207
|
}
|
|
303
208
|
if (msg.tool_calls) {
|
|
304
209
|
for (let j = 0; j < msg.tool_calls.length; j++) {
|
|
305
|
-
nodes.push(
|
|
210
|
+
nodes.push(
|
|
211
|
+
React.createElement(ToolCall, {
|
|
212
|
+
key: `m-${i}-tc-${j}`,
|
|
213
|
+
tc: msg.tool_calls[j],
|
|
214
|
+
})
|
|
215
|
+
);
|
|
306
216
|
}
|
|
307
217
|
}
|
|
308
218
|
return nodes;
|
|
309
219
|
}
|
|
310
220
|
if (msg.role === "tool") {
|
|
311
|
-
return [
|
|
221
|
+
return [
|
|
222
|
+
React.createElement(ToolResult, {
|
|
223
|
+
key: `m-${i}`,
|
|
224
|
+
msg,
|
|
225
|
+
duration: durations[msg.tool_call_id],
|
|
226
|
+
}),
|
|
227
|
+
];
|
|
312
228
|
}
|
|
313
229
|
return [];
|
|
314
230
|
}),
|
|
315
231
|
streaming &&
|
|
316
|
-
(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
React.createElement(
|
|
325
|
-
Box,
|
|
326
|
-
null,
|
|
327
|
-
React.createElement(
|
|
328
|
-
Text,
|
|
329
|
-
{ color: "cyan", bold: true },
|
|
330
|
-
"Atom:"
|
|
331
|
-
),
|
|
332
|
-
React.createElement(Text, null, " ", firstLine)
|
|
333
|
-
),
|
|
334
|
-
rest ? React.createElement(Text, null, rest) : null
|
|
335
|
-
);
|
|
336
|
-
})(),
|
|
337
|
-
...streamingTools.map((tc, j) => renderToolCall(tc, `streaming-tc-${j}`)),
|
|
232
|
+
React.createElement(AssistantMessage, {
|
|
233
|
+
key: "streaming",
|
|
234
|
+
content: streaming,
|
|
235
|
+
streaming: true,
|
|
236
|
+
}),
|
|
237
|
+
...streamingTools.map((tc, j) =>
|
|
238
|
+
React.createElement(ToolCall, { key: `streaming-tc-${j}`, tc })
|
|
239
|
+
),
|
|
338
240
|
running &&
|
|
339
|
-
React.createElement(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
Text,
|
|
344
|
-
{ dimColor: true },
|
|
345
|
-
`⎿ running ${formatMs(running.elapsed)}`
|
|
346
|
-
)
|
|
347
|
-
),
|
|
241
|
+
React.createElement(RunningIndicator, {
|
|
242
|
+
key: "running",
|
|
243
|
+
elapsed: running.elapsed,
|
|
244
|
+
}),
|
|
348
245
|
error &&
|
|
349
246
|
React.createElement(Text, { key: "error", color: "red" }, `Error: ${error}`),
|
|
350
247
|
confirm
|
package/src/setup.js
DELETED