bashbros 0.1.0
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/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/audit-MCFNGOIM.js +11 -0
- package/dist/audit-MCFNGOIM.js.map +1 -0
- package/dist/chunk-43W3RVEL.js +2910 -0
- package/dist/chunk-43W3RVEL.js.map +1 -0
- package/dist/chunk-4R4GV5V2.js +213 -0
- package/dist/chunk-4R4GV5V2.js.map +1 -0
- package/dist/chunk-7OCVIDC7.js +12 -0
- package/dist/chunk-7OCVIDC7.js.map +1 -0
- package/dist/chunk-CSRPOGHY.js +354 -0
- package/dist/chunk-CSRPOGHY.js.map +1 -0
- package/dist/chunk-DEAF6PYM.js +212 -0
- package/dist/chunk-DEAF6PYM.js.map +1 -0
- package/dist/chunk-DLP2O6PN.js +273 -0
- package/dist/chunk-DLP2O6PN.js.map +1 -0
- package/dist/chunk-GD5VNHIN.js +519 -0
- package/dist/chunk-GD5VNHIN.js.map +1 -0
- package/dist/chunk-ID2O2QTI.js +269 -0
- package/dist/chunk-ID2O2QTI.js.map +1 -0
- package/dist/chunk-J37RHCFJ.js +357 -0
- package/dist/chunk-J37RHCFJ.js.map +1 -0
- package/dist/chunk-SB4JS3GU.js +456 -0
- package/dist/chunk-SB4JS3GU.js.map +1 -0
- package/dist/chunk-SG752FZC.js +200 -0
- package/dist/chunk-SG752FZC.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2448 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-CZMIGNPF.js +13 -0
- package/dist/config-CZMIGNPF.js.map +1 -0
- package/dist/config-parser-XHE7BC7H.js +13 -0
- package/dist/config-parser-XHE7BC7H.js.map +1 -0
- package/dist/db-EHQDB5OL.js +11 -0
- package/dist/db-EHQDB5OL.js.map +1 -0
- package/dist/display-IN4NRJJS.js +18 -0
- package/dist/display-IN4NRJJS.js.map +1 -0
- package/dist/engine-PKLXW6OF.js +9 -0
- package/dist/engine-PKLXW6OF.js.map +1 -0
- package/dist/index.d.ts +1498 -0
- package/dist/index.js +552 -0
- package/dist/index.js.map +1 -0
- package/dist/moltbot-DXZFVK3X.js +11 -0
- package/dist/moltbot-DXZFVK3X.js.map +1 -0
- package/dist/ollama-HY35OHW4.js +9 -0
- package/dist/ollama-HY35OHW4.js.map +1 -0
- package/dist/risk-scorer-Y6KF2XCZ.js +9 -0
- package/dist/risk-scorer-Y6KF2XCZ.js.map +1 -0
- package/dist/static/index.html +410 -0
- package/package.json +68 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>BashBros Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
16
|
+
background: #1a1a2e;
|
|
17
|
+
color: #eaeaea;
|
|
18
|
+
min-height: 100vh;
|
|
19
|
+
padding: 20px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.header {
|
|
23
|
+
display: flex;
|
|
24
|
+
justify-content: space-between;
|
|
25
|
+
align-items: center;
|
|
26
|
+
margin-bottom: 30px;
|
|
27
|
+
padding-bottom: 20px;
|
|
28
|
+
border-bottom: 1px solid #2d2d44;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.header h1 {
|
|
32
|
+
font-size: 24px;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
color: #fff;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.connection-status {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 8px;
|
|
41
|
+
font-size: 14px;
|
|
42
|
+
color: #888;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.status-dot {
|
|
46
|
+
width: 10px;
|
|
47
|
+
height: 10px;
|
|
48
|
+
border-radius: 50%;
|
|
49
|
+
background: #666;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.status-dot.connected {
|
|
53
|
+
background: #4ade80;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.status-dot.disconnected {
|
|
57
|
+
background: #ef4444;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.stats-grid {
|
|
61
|
+
display: grid;
|
|
62
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
63
|
+
gap: 20px;
|
|
64
|
+
margin-bottom: 30px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.stat-card {
|
|
68
|
+
background: #16213e;
|
|
69
|
+
border-radius: 12px;
|
|
70
|
+
padding: 20px;
|
|
71
|
+
border: 1px solid #2d2d44;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.stat-card .label {
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
color: #888;
|
|
77
|
+
margin-bottom: 8px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.stat-card .value {
|
|
81
|
+
font-size: 32px;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
color: #fff;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.stat-card.warning .value {
|
|
87
|
+
color: #fbbf24;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.stat-card.error .value {
|
|
91
|
+
color: #ef4444;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.stat-card.success .value {
|
|
95
|
+
color: #4ade80;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.section {
|
|
99
|
+
background: #16213e;
|
|
100
|
+
border-radius: 12px;
|
|
101
|
+
padding: 20px;
|
|
102
|
+
border: 1px solid #2d2d44;
|
|
103
|
+
margin-bottom: 20px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.section h2 {
|
|
107
|
+
font-size: 18px;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
margin-bottom: 16px;
|
|
110
|
+
color: #fff;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.activity-list {
|
|
114
|
+
list-style: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.activity-item {
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: flex-start;
|
|
120
|
+
gap: 12px;
|
|
121
|
+
padding: 12px 0;
|
|
122
|
+
border-bottom: 1px solid #2d2d44;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.activity-item:last-child {
|
|
126
|
+
border-bottom: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.activity-icon {
|
|
130
|
+
width: 32px;
|
|
131
|
+
height: 32px;
|
|
132
|
+
border-radius: 8px;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
font-size: 14px;
|
|
137
|
+
flex-shrink: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.activity-icon.info {
|
|
141
|
+
background: #1e40af;
|
|
142
|
+
color: #60a5fa;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.activity-icon.warning {
|
|
146
|
+
background: #854d0e;
|
|
147
|
+
color: #fbbf24;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.activity-icon.error {
|
|
151
|
+
background: #7f1d1d;
|
|
152
|
+
color: #ef4444;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.activity-icon.debug {
|
|
156
|
+
background: #374151;
|
|
157
|
+
color: #9ca3af;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.activity-content {
|
|
161
|
+
flex: 1;
|
|
162
|
+
min-width: 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.activity-message {
|
|
166
|
+
font-size: 14px;
|
|
167
|
+
color: #eaeaea;
|
|
168
|
+
word-break: break-word;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.activity-meta {
|
|
172
|
+
display: flex;
|
|
173
|
+
gap: 16px;
|
|
174
|
+
margin-top: 4px;
|
|
175
|
+
font-size: 12px;
|
|
176
|
+
color: #666;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.activity-meta span {
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
gap: 4px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.empty-state {
|
|
186
|
+
text-align: center;
|
|
187
|
+
padding: 40px 20px;
|
|
188
|
+
color: #666;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.empty-state p {
|
|
192
|
+
font-size: 14px;
|
|
193
|
+
}
|
|
194
|
+
</style>
|
|
195
|
+
</head>
|
|
196
|
+
<body>
|
|
197
|
+
<div class="header">
|
|
198
|
+
<h1>BashBros Dashboard</h1>
|
|
199
|
+
<div class="connection-status">
|
|
200
|
+
<span class="status-dot" id="connectionDot"></span>
|
|
201
|
+
<span id="connectionText">Connecting...</span>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div class="stats-grid">
|
|
206
|
+
<div class="stat-card">
|
|
207
|
+
<div class="label">Total Events</div>
|
|
208
|
+
<div class="value" id="totalEvents">0</div>
|
|
209
|
+
</div>
|
|
210
|
+
<div class="stat-card warning">
|
|
211
|
+
<div class="label">Pending Blocks</div>
|
|
212
|
+
<div class="value" id="pendingBlocks">0</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="stat-card success">
|
|
215
|
+
<div class="label">Active Connectors</div>
|
|
216
|
+
<div class="value" id="activeConnectors">0</div>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="stat-card error">
|
|
219
|
+
<div class="label">Errors</div>
|
|
220
|
+
<div class="value" id="errors">0</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="section">
|
|
225
|
+
<h2>Recent Activity</h2>
|
|
226
|
+
<ul class="activity-list" id="activityList">
|
|
227
|
+
<li class="empty-state">
|
|
228
|
+
<p>No recent activity</p>
|
|
229
|
+
</li>
|
|
230
|
+
</ul>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<script>
|
|
234
|
+
// Dashboard state
|
|
235
|
+
let ws = null;
|
|
236
|
+
let reconnectTimeout = null;
|
|
237
|
+
|
|
238
|
+
// DOM elements
|
|
239
|
+
const connectionDot = document.getElementById('connectionDot');
|
|
240
|
+
const connectionText = document.getElementById('connectionText');
|
|
241
|
+
const totalEventsEl = document.getElementById('totalEvents');
|
|
242
|
+
const pendingBlocksEl = document.getElementById('pendingBlocks');
|
|
243
|
+
const activeConnectorsEl = document.getElementById('activeConnectors');
|
|
244
|
+
const errorsEl = document.getElementById('errors');
|
|
245
|
+
const activityList = document.getElementById('activityList');
|
|
246
|
+
|
|
247
|
+
// Update connection status
|
|
248
|
+
function setConnectionStatus(connected) {
|
|
249
|
+
connectionDot.className = 'status-dot ' + (connected ? 'connected' : 'disconnected');
|
|
250
|
+
connectionText.textContent = connected ? 'Connected' : 'Disconnected';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Update stats display
|
|
254
|
+
function updateStats(stats) {
|
|
255
|
+
totalEventsEl.textContent = stats.totalEvents || 0;
|
|
256
|
+
pendingBlocksEl.textContent = stats.pendingBlocks || 0;
|
|
257
|
+
activeConnectorsEl.textContent = stats.connectorCount || 0;
|
|
258
|
+
errorsEl.textContent = (stats.eventsByLevel && stats.eventsByLevel.error) || 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Format timestamp
|
|
262
|
+
function formatTime(timestamp) {
|
|
263
|
+
const date = new Date(timestamp);
|
|
264
|
+
return date.toLocaleTimeString();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Get icon class based on level
|
|
268
|
+
function getIconClass(level) {
|
|
269
|
+
switch (level) {
|
|
270
|
+
case 'error': return 'error';
|
|
271
|
+
case 'warning': return 'warning';
|
|
272
|
+
case 'debug': return 'debug';
|
|
273
|
+
default: return 'info';
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Get icon symbol based on level
|
|
278
|
+
function getIconSymbol(level) {
|
|
279
|
+
switch (level) {
|
|
280
|
+
case 'error': return '!';
|
|
281
|
+
case 'warning': return '!';
|
|
282
|
+
case 'debug': return '#';
|
|
283
|
+
default: return 'i';
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Update activity list
|
|
288
|
+
function updateActivity(events) {
|
|
289
|
+
if (!events || events.length === 0) {
|
|
290
|
+
activityList.innerHTML = '<li class="empty-state"><p>No recent activity</p></li>';
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
activityList.innerHTML = events.slice(0, 20).map(event => `
|
|
295
|
+
<li class="activity-item">
|
|
296
|
+
<div class="activity-icon ${getIconClass(event.level)}">
|
|
297
|
+
${getIconSymbol(event.level)}
|
|
298
|
+
</div>
|
|
299
|
+
<div class="activity-content">
|
|
300
|
+
<div class="activity-message">${escapeHtml(event.message)}</div>
|
|
301
|
+
<div class="activity-meta">
|
|
302
|
+
<span>${event.source}</span>
|
|
303
|
+
<span>${event.category}</span>
|
|
304
|
+
<span>${formatTime(event.timestamp)}</span>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
</li>
|
|
308
|
+
`).join('');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Escape HTML to prevent XSS
|
|
312
|
+
function escapeHtml(text) {
|
|
313
|
+
const div = document.createElement('div');
|
|
314
|
+
div.textContent = text;
|
|
315
|
+
return div.innerHTML;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Fetch stats from API
|
|
319
|
+
async function fetchStats() {
|
|
320
|
+
try {
|
|
321
|
+
const response = await fetch('/api/stats');
|
|
322
|
+
if (response.ok) {
|
|
323
|
+
const stats = await response.json();
|
|
324
|
+
updateStats(stats);
|
|
325
|
+
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('Failed to fetch stats:', error);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Fetch events from API
|
|
332
|
+
async function fetchEvents() {
|
|
333
|
+
try {
|
|
334
|
+
const response = await fetch('/api/events?limit=20');
|
|
335
|
+
if (response.ok) {
|
|
336
|
+
const events = await response.json();
|
|
337
|
+
updateActivity(events);
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error('Failed to fetch events:', error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Connect to WebSocket
|
|
345
|
+
function connectWebSocket() {
|
|
346
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
347
|
+
const wsUrl = `${protocol}//${window.location.host}`;
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
ws = new WebSocket(wsUrl);
|
|
351
|
+
|
|
352
|
+
ws.onopen = function() {
|
|
353
|
+
setConnectionStatus(true);
|
|
354
|
+
if (reconnectTimeout) {
|
|
355
|
+
clearTimeout(reconnectTimeout);
|
|
356
|
+
reconnectTimeout = null;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
ws.onmessage = function(event) {
|
|
361
|
+
try {
|
|
362
|
+
const message = JSON.parse(event.data);
|
|
363
|
+
if (message.type === 'stats') {
|
|
364
|
+
updateStats(message.data);
|
|
365
|
+
} else if (message.type === 'event') {
|
|
366
|
+
fetchEvents();
|
|
367
|
+
fetchStats();
|
|
368
|
+
} else if (message.type === 'block-approved' || message.type === 'block-denied') {
|
|
369
|
+
fetchStats();
|
|
370
|
+
}
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error('Failed to parse WebSocket message:', error);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
ws.onclose = function() {
|
|
377
|
+
setConnectionStatus(false);
|
|
378
|
+
ws = null;
|
|
379
|
+
// Attempt to reconnect after 5 seconds
|
|
380
|
+
reconnectTimeout = setTimeout(connectWebSocket, 5000);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
ws.onerror = function() {
|
|
384
|
+
setConnectionStatus(false);
|
|
385
|
+
};
|
|
386
|
+
} catch (error) {
|
|
387
|
+
console.error('Failed to connect WebSocket:', error);
|
|
388
|
+
setConnectionStatus(false);
|
|
389
|
+
reconnectTimeout = setTimeout(connectWebSocket, 5000);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Initialize dashboard
|
|
394
|
+
function init() {
|
|
395
|
+
fetchStats();
|
|
396
|
+
fetchEvents();
|
|
397
|
+
connectWebSocket();
|
|
398
|
+
|
|
399
|
+
// Auto-refresh every 30 seconds
|
|
400
|
+
setInterval(() => {
|
|
401
|
+
fetchStats();
|
|
402
|
+
fetchEvents();
|
|
403
|
+
}, 30000);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Start when DOM is ready
|
|
407
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
408
|
+
</script>
|
|
409
|
+
</body>
|
|
410
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bashbros",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Bash Agent Helper",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"bashbros": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup && npm run copy-static",
|
|
18
|
+
"copy-static": "node -e \"const fs=require('fs');fs.mkdirSync('dist/static',{recursive:true});fs.cpSync('src/dashboard/static','dist/static',{recursive:true})\"",
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"test": "vitest",
|
|
21
|
+
"lint": "eslint src/",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"bash",
|
|
26
|
+
"cli",
|
|
27
|
+
"agent",
|
|
28
|
+
"security",
|
|
29
|
+
"pty",
|
|
30
|
+
"claude",
|
|
31
|
+
"ai",
|
|
32
|
+
"automation"
|
|
33
|
+
],
|
|
34
|
+
"author": "GhostPeony",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/GhostPeony/bashbros.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://bashbros.ai",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/GhostPeony/bashbros/issues"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"better-sqlite3": "^12.6.2",
|
|
49
|
+
"chalk": "^5.3.0",
|
|
50
|
+
"commander": "^12.1.0",
|
|
51
|
+
"express": "^5.2.1",
|
|
52
|
+
"inquirer": "^9.2.0",
|
|
53
|
+
"node-pty": "^1.0.0",
|
|
54
|
+
"winston": "^3.13.0",
|
|
55
|
+
"ws": "^8.19.0",
|
|
56
|
+
"yaml": "^2.4.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
60
|
+
"@types/express": "^5.0.6",
|
|
61
|
+
"@types/node": "^20.0.0",
|
|
62
|
+
"@types/ws": "^8.18.1",
|
|
63
|
+
"eslint": "^9.0.0",
|
|
64
|
+
"tsup": "^8.0.0",
|
|
65
|
+
"typescript": "^5.4.0",
|
|
66
|
+
"vitest": "^1.6.0"
|
|
67
|
+
}
|
|
68
|
+
}
|