fastclaw-cli 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/README.md +117 -0
- package/dist/index.js +1413 -0
- package/dist/sdk.d.ts +312 -0
- package/dist/sdk.js +409 -0
- package/package.json +50 -0
package/dist/sdk.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var DEFAULT_URL = "https://claw.yesy.dev";
|
|
3
|
+
var API_BASE = "/bot/api/v1";
|
|
4
|
+
var MODEL_PRESETS = {
|
|
5
|
+
google: {
|
|
6
|
+
name: "google",
|
|
7
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
8
|
+
models: [
|
|
9
|
+
{
|
|
10
|
+
id: "gemini-2.5-flash",
|
|
11
|
+
name: "Gemini 2.5 Flash",
|
|
12
|
+
reasoning: true,
|
|
13
|
+
input: ["text", "image"],
|
|
14
|
+
context_window: 1048576,
|
|
15
|
+
max_tokens: 65536
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "gemini-2.5-pro",
|
|
19
|
+
name: "Gemini 2.5 Pro",
|
|
20
|
+
reasoning: true,
|
|
21
|
+
input: ["text", "image"],
|
|
22
|
+
context_window: 1048576,
|
|
23
|
+
max_tokens: 65536
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
anthropic: {
|
|
28
|
+
name: "anthropic",
|
|
29
|
+
baseUrl: "https://api.anthropic.com",
|
|
30
|
+
auth: "api-key",
|
|
31
|
+
api: "anthropic-messages",
|
|
32
|
+
models: [
|
|
33
|
+
{
|
|
34
|
+
id: "claude-sonnet-4-20250514",
|
|
35
|
+
name: "Claude Sonnet 4",
|
|
36
|
+
reasoning: false,
|
|
37
|
+
input: ["text", "image"],
|
|
38
|
+
context_window: 2e5,
|
|
39
|
+
max_tokens: 8192
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
openai: {
|
|
44
|
+
name: "openai",
|
|
45
|
+
baseUrl: "https://api.openai.com/v1",
|
|
46
|
+
auth: "api-key",
|
|
47
|
+
api: "openai-completions",
|
|
48
|
+
models: [
|
|
49
|
+
{
|
|
50
|
+
id: "gpt-5.1",
|
|
51
|
+
name: "GPT-5.1",
|
|
52
|
+
reasoning: false,
|
|
53
|
+
input: ["text", "image"],
|
|
54
|
+
context_window: 1048576,
|
|
55
|
+
max_tokens: 32768
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
minimax: {
|
|
60
|
+
name: "minimax",
|
|
61
|
+
baseUrl: "https://api.minimax.chat/v1",
|
|
62
|
+
auth: "api-key",
|
|
63
|
+
api: "openai-completions",
|
|
64
|
+
models: [
|
|
65
|
+
{
|
|
66
|
+
id: "MiniMax-Text-01",
|
|
67
|
+
name: "MiniMax Text 01",
|
|
68
|
+
reasoning: false,
|
|
69
|
+
input: ["text"],
|
|
70
|
+
context_window: 1e6,
|
|
71
|
+
max_tokens: 65536
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var PROVIDER_DEFAULTS = {
|
|
77
|
+
google: "google/gemini-2.5-flash",
|
|
78
|
+
anthropic: "anthropic/claude-sonnet-4-20250514",
|
|
79
|
+
openai: "openai/gpt-5.1",
|
|
80
|
+
minimax: "minimax/MiniMax-Text-01"
|
|
81
|
+
};
|
|
82
|
+
var CHANNEL_FIELDS = {
|
|
83
|
+
feishu: {
|
|
84
|
+
required: ["appId", "appSecret"],
|
|
85
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
86
|
+
},
|
|
87
|
+
telegram: {
|
|
88
|
+
required: ["botToken"],
|
|
89
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
90
|
+
},
|
|
91
|
+
slack: {
|
|
92
|
+
required: ["botToken", "appToken"],
|
|
93
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
94
|
+
},
|
|
95
|
+
discord: {
|
|
96
|
+
required: ["botToken"],
|
|
97
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
98
|
+
},
|
|
99
|
+
whatsapp: {
|
|
100
|
+
required: ["botToken"],
|
|
101
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
102
|
+
},
|
|
103
|
+
line: {
|
|
104
|
+
required: ["botToken", "channelSecret"],
|
|
105
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
106
|
+
},
|
|
107
|
+
teams: {
|
|
108
|
+
required: ["appId", "appPassword"],
|
|
109
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var DM_POLICIES = ["open", "pairing", "allowlist", "disabled"];
|
|
113
|
+
var GROUP_POLICIES = ["open", "allowlist", "disabled"];
|
|
114
|
+
|
|
115
|
+
// src/utils/error.ts
|
|
116
|
+
import chalk from "chalk";
|
|
117
|
+
var ApiError = class extends Error {
|
|
118
|
+
constructor(statusCode, apiCode, message) {
|
|
119
|
+
super(message);
|
|
120
|
+
this.statusCode = statusCode;
|
|
121
|
+
this.apiCode = apiCode;
|
|
122
|
+
this.name = "ApiError";
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/client.ts
|
|
127
|
+
var FastClawClient = class {
|
|
128
|
+
baseUrl;
|
|
129
|
+
adminToken;
|
|
130
|
+
appToken;
|
|
131
|
+
constructor(opts) {
|
|
132
|
+
this.baseUrl = opts.url.replace(/\/+$/, "") + API_BASE;
|
|
133
|
+
this.adminToken = opts.adminToken;
|
|
134
|
+
this.appToken = opts.appToken;
|
|
135
|
+
}
|
|
136
|
+
// ── Helpers ──
|
|
137
|
+
async request(method, path, opts) {
|
|
138
|
+
const authType = opts?.auth ?? "app";
|
|
139
|
+
const token = authType === "admin" ? this.adminToken : this.appToken;
|
|
140
|
+
if (!token) {
|
|
141
|
+
throw new Error(`No ${authType} token configured. Set via --${authType}-token, env, or 'fastclaw config set'.`);
|
|
142
|
+
}
|
|
143
|
+
let url = this.baseUrl + path;
|
|
144
|
+
if (opts?.query) {
|
|
145
|
+
const params = new URLSearchParams();
|
|
146
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
147
|
+
if (v !== void 0 && v !== "") params.set(k, v);
|
|
148
|
+
}
|
|
149
|
+
const qs = params.toString();
|
|
150
|
+
if (qs) url += "?" + qs;
|
|
151
|
+
}
|
|
152
|
+
const headers = {
|
|
153
|
+
Authorization: `Bearer ${token}`
|
|
154
|
+
};
|
|
155
|
+
let bodyStr;
|
|
156
|
+
if (opts?.body !== void 0) {
|
|
157
|
+
headers["Content-Type"] = "application/json";
|
|
158
|
+
bodyStr = JSON.stringify(opts.body);
|
|
159
|
+
}
|
|
160
|
+
const res = await fetch(url, { method, headers, body: bodyStr });
|
|
161
|
+
if (!res.ok) {
|
|
162
|
+
let msg = res.statusText;
|
|
163
|
+
try {
|
|
164
|
+
const j = await res.json();
|
|
165
|
+
if (j.message) msg = j.message;
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
throw new ApiError(res.status, res.status, msg);
|
|
169
|
+
}
|
|
170
|
+
const text = await res.text();
|
|
171
|
+
if (!text) return void 0;
|
|
172
|
+
const json = JSON.parse(text);
|
|
173
|
+
if (json && typeof json === "object" && "code" in json && json.code === 0 && "data" in json) {
|
|
174
|
+
return json.data;
|
|
175
|
+
}
|
|
176
|
+
return json;
|
|
177
|
+
}
|
|
178
|
+
// ── Admin: Apps ──
|
|
179
|
+
async createApp(req) {
|
|
180
|
+
return this.request("POST", "/admin/apps", { body: req, auth: "admin" });
|
|
181
|
+
}
|
|
182
|
+
async listApps() {
|
|
183
|
+
return this.request("GET", "/admin/apps", { auth: "admin" });
|
|
184
|
+
}
|
|
185
|
+
async getApp(id) {
|
|
186
|
+
return this.request("GET", `/admin/apps/${id}`, { auth: "admin" });
|
|
187
|
+
}
|
|
188
|
+
async updateApp(id, req) {
|
|
189
|
+
return this.request("PUT", `/admin/apps/${id}`, { body: req, auth: "admin" });
|
|
190
|
+
}
|
|
191
|
+
async deleteApp(id) {
|
|
192
|
+
return this.request("DELETE", `/admin/apps/${id}`, { auth: "admin" });
|
|
193
|
+
}
|
|
194
|
+
async rotateAppToken(id) {
|
|
195
|
+
return this.request("POST", `/admin/apps/${id}/reset-token`, { auth: "admin" });
|
|
196
|
+
}
|
|
197
|
+
// ── Admin: Bot Upgrade ──
|
|
198
|
+
async upgradeBot(id, req) {
|
|
199
|
+
return this.request("POST", `/admin/bots/${id}/upgrade`, { body: req ?? {}, auth: "admin" });
|
|
200
|
+
}
|
|
201
|
+
async upgradeAllBots(req) {
|
|
202
|
+
return this.request("POST", "/admin/bots/upgrade", { body: req ?? {}, auth: "admin" });
|
|
203
|
+
}
|
|
204
|
+
// ── Bots ──
|
|
205
|
+
async createBot(req) {
|
|
206
|
+
return this.request("POST", "/bots", { body: req });
|
|
207
|
+
}
|
|
208
|
+
async listBots(userId) {
|
|
209
|
+
return this.request("GET", "/bots", { query: { user_id: userId } });
|
|
210
|
+
}
|
|
211
|
+
async getBot(id) {
|
|
212
|
+
return this.request("GET", `/bots/${id}`);
|
|
213
|
+
}
|
|
214
|
+
async updateBot(id, req) {
|
|
215
|
+
return this.request("PUT", `/bots/${id}`, { body: req });
|
|
216
|
+
}
|
|
217
|
+
async deleteBot(id) {
|
|
218
|
+
return this.request("DELETE", `/bots/${id}`);
|
|
219
|
+
}
|
|
220
|
+
// ── Bot Lifecycle ──
|
|
221
|
+
async startBot(id) {
|
|
222
|
+
return this.request("POST", `/bots/${id}/start`);
|
|
223
|
+
}
|
|
224
|
+
async stopBot(id) {
|
|
225
|
+
return this.request("POST", `/bots/${id}/stop`);
|
|
226
|
+
}
|
|
227
|
+
async restartBot(id) {
|
|
228
|
+
return this.request("POST", `/bots/${id}/restart`);
|
|
229
|
+
}
|
|
230
|
+
async getBotStatus(id) {
|
|
231
|
+
return this.request("GET", `/bots/${id}/status`);
|
|
232
|
+
}
|
|
233
|
+
async getBotConnect(id) {
|
|
234
|
+
return this.request("GET", `/bots/${id}/connect`);
|
|
235
|
+
}
|
|
236
|
+
async resetBotToken(id) {
|
|
237
|
+
return this.request("POST", `/bots/${id}/reset-token`);
|
|
238
|
+
}
|
|
239
|
+
// ── Model Providers ──
|
|
240
|
+
async listProviders(botId) {
|
|
241
|
+
return this.request("GET", `/bots/${botId}/config/models`);
|
|
242
|
+
}
|
|
243
|
+
async addProvider(botId, provider) {
|
|
244
|
+
return this.request("POST", `/bots/${botId}/config/models`, { body: provider });
|
|
245
|
+
}
|
|
246
|
+
async getProvider(botId, name) {
|
|
247
|
+
return this.request("GET", `/bots/${botId}/config/models/${name}`);
|
|
248
|
+
}
|
|
249
|
+
async updateProvider(botId, name, provider) {
|
|
250
|
+
return this.request("PUT", `/bots/${botId}/config/models/${name}`, { body: provider });
|
|
251
|
+
}
|
|
252
|
+
async deleteProvider(botId, name) {
|
|
253
|
+
return this.request("DELETE", `/bots/${botId}/config/models/${name}`);
|
|
254
|
+
}
|
|
255
|
+
// ── Model Defaults ──
|
|
256
|
+
async getDefaults(botId) {
|
|
257
|
+
return this.request("GET", `/bots/${botId}/config/defaults`);
|
|
258
|
+
}
|
|
259
|
+
async setDefaults(botId, defaults) {
|
|
260
|
+
return this.request("PUT", `/bots/${botId}/config/defaults`, { body: defaults });
|
|
261
|
+
}
|
|
262
|
+
// ── Channels ──
|
|
263
|
+
async addChannel(botId, req) {
|
|
264
|
+
return this.request("POST", `/bots/${botId}/channels`, { body: req });
|
|
265
|
+
}
|
|
266
|
+
async listChannels(botId) {
|
|
267
|
+
return this.request("GET", `/bots/${botId}/channels`);
|
|
268
|
+
}
|
|
269
|
+
async removeChannel(botId, channel, account) {
|
|
270
|
+
const query = {};
|
|
271
|
+
if (account) query.account = account;
|
|
272
|
+
return this.request("DELETE", `/bots/${botId}/channels/${channel}`, { query });
|
|
273
|
+
}
|
|
274
|
+
// ── Channel Pairing ──
|
|
275
|
+
async listPairing(botId, channel) {
|
|
276
|
+
return this.request("GET", `/bots/${botId}/channels/${channel}/pairing`);
|
|
277
|
+
}
|
|
278
|
+
async approvePairing(botId, channel, req) {
|
|
279
|
+
return this.request("POST", `/bots/${botId}/channels/${channel}/pairing/approve`, { body: req });
|
|
280
|
+
}
|
|
281
|
+
async revokePairing(botId, channel, req) {
|
|
282
|
+
return this.request("POST", `/bots/${botId}/channels/${channel}/pairing/revoke`, { body: req });
|
|
283
|
+
}
|
|
284
|
+
async listPairedUsers(botId, channel) {
|
|
285
|
+
return this.request("GET", `/bots/${botId}/channels/${channel}/pairing/users`);
|
|
286
|
+
}
|
|
287
|
+
// ── Devices ──
|
|
288
|
+
async listDevices(botId, opts) {
|
|
289
|
+
const query = {};
|
|
290
|
+
if (opts?.status) query.status = opts.status;
|
|
291
|
+
if (opts?.client_mode) query.client_mode = opts.client_mode;
|
|
292
|
+
return this.request("GET", `/bots/${botId}/devices`, { query });
|
|
293
|
+
}
|
|
294
|
+
async approveDevice(botId, requestId) {
|
|
295
|
+
return this.request("POST", `/bots/${botId}/devices/${requestId}/approve`);
|
|
296
|
+
}
|
|
297
|
+
async revokeDevice(botId, deviceId, role) {
|
|
298
|
+
const query = {};
|
|
299
|
+
if (role) query.role = role;
|
|
300
|
+
return this.request("DELETE", `/bots/${botId}/devices/${deviceId}`, { query });
|
|
301
|
+
}
|
|
302
|
+
// ── Skills ──
|
|
303
|
+
async listSkills(botId) {
|
|
304
|
+
return this.request("GET", `/bots/${botId}/skills`);
|
|
305
|
+
}
|
|
306
|
+
async setSkill(botId, name, req) {
|
|
307
|
+
return this.request("PUT", `/bots/${botId}/skills/${name}`, { body: req });
|
|
308
|
+
}
|
|
309
|
+
async deleteSkill(botId, name) {
|
|
310
|
+
return this.request("DELETE", `/bots/${botId}/skills/${name}`);
|
|
311
|
+
}
|
|
312
|
+
// ── Utility: wait for bot ready ──
|
|
313
|
+
async waitForReady(botId, timeoutMs = 12e4, intervalMs = 3e3) {
|
|
314
|
+
const start = Date.now();
|
|
315
|
+
while (Date.now() - start < timeoutMs) {
|
|
316
|
+
const status = await this.getBotStatus(botId);
|
|
317
|
+
if (status.ready) return status;
|
|
318
|
+
if (status.status === "error") throw new Error("Bot entered error state");
|
|
319
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
320
|
+
}
|
|
321
|
+
throw new Error(`Bot not ready after ${timeoutMs / 1e3}s`);
|
|
322
|
+
}
|
|
323
|
+
// ── Health check ──
|
|
324
|
+
async health() {
|
|
325
|
+
try {
|
|
326
|
+
const url = this.baseUrl.replace(API_BASE, "") + "/health";
|
|
327
|
+
const res = await fetch(url);
|
|
328
|
+
return res.ok;
|
|
329
|
+
} catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// src/config.ts
|
|
336
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
337
|
+
import { join } from "path";
|
|
338
|
+
import { homedir } from "os";
|
|
339
|
+
var CONFIG_DIR = join(homedir(), ".fastclaw");
|
|
340
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
341
|
+
function loadConfig() {
|
|
342
|
+
try {
|
|
343
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
344
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
345
|
+
return JSON.parse(raw);
|
|
346
|
+
} catch {
|
|
347
|
+
return {};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function saveConfig(config) {
|
|
351
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
352
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
353
|
+
}
|
|
354
|
+
function updateConfig(updates) {
|
|
355
|
+
const config = loadConfig();
|
|
356
|
+
Object.assign(config, updates);
|
|
357
|
+
saveConfig(config);
|
|
358
|
+
return config;
|
|
359
|
+
}
|
|
360
|
+
function resolveConfig(opts) {
|
|
361
|
+
const file = loadConfig();
|
|
362
|
+
return {
|
|
363
|
+
url: opts.url || process.env.FASTCLAW_URL || file.url || DEFAULT_URL,
|
|
364
|
+
adminToken: opts.adminToken || process.env.FASTCLAW_ADMIN_TOKEN || file.admin_token,
|
|
365
|
+
appToken: opts.appToken || process.env.FASTCLAW_APP_TOKEN || file.app_token,
|
|
366
|
+
userId: opts.userId || process.env.FASTCLAW_USER_ID || file.default_user_id || "default",
|
|
367
|
+
botId: opts.botId || process.env.FASTCLAW_BOT_ID || file.default_bot_id,
|
|
368
|
+
json: opts.json ?? false
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function getConfigPath() {
|
|
372
|
+
return CONFIG_FILE;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/utils/validators.ts
|
|
376
|
+
var SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
377
|
+
function isValidSlug(slug) {
|
|
378
|
+
return slug.length >= 1 && slug.length <= 50 && SLUG_REGEX.test(slug);
|
|
379
|
+
}
|
|
380
|
+
function isValidUrl(url) {
|
|
381
|
+
try {
|
|
382
|
+
new URL(url);
|
|
383
|
+
return true;
|
|
384
|
+
} catch {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function slugify(name) {
|
|
389
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
390
|
+
}
|
|
391
|
+
export {
|
|
392
|
+
API_BASE,
|
|
393
|
+
ApiError,
|
|
394
|
+
CHANNEL_FIELDS,
|
|
395
|
+
DEFAULT_URL,
|
|
396
|
+
DM_POLICIES,
|
|
397
|
+
FastClawClient,
|
|
398
|
+
GROUP_POLICIES,
|
|
399
|
+
MODEL_PRESETS,
|
|
400
|
+
PROVIDER_DEFAULTS,
|
|
401
|
+
getConfigPath,
|
|
402
|
+
isValidSlug,
|
|
403
|
+
isValidUrl,
|
|
404
|
+
loadConfig,
|
|
405
|
+
resolveConfig,
|
|
406
|
+
saveConfig,
|
|
407
|
+
slugify,
|
|
408
|
+
updateConfig
|
|
409
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fastclaw-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI and SDK for managing FastClaw bots",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/sdk.js",
|
|
7
|
+
"types": "dist/sdk.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/sdk.js",
|
|
11
|
+
"types": "./dist/sdk.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"fastclaw": "dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"start": "node dist/index.js",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18.0.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"fastclaw",
|
|
31
|
+
"bot",
|
|
32
|
+
"cli",
|
|
33
|
+
"sdk",
|
|
34
|
+
"openclaw"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@inquirer/prompts": "^7.0.0",
|
|
39
|
+
"boxen": "^8.0.1",
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"cli-table3": "^0.6.5",
|
|
42
|
+
"commander": "^13.0.0",
|
|
43
|
+
"ora": "^8.1.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "^5.5.0"
|
|
49
|
+
}
|
|
50
|
+
}
|