@bonnard/agentops 0.1.1 → 0.2.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/dist/bin/agentops.mjs +245 -70
- package/package.json +13 -12
package/dist/bin/agentops.mjs
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import pc from "picocolors";
|
|
4
|
+
import crypto from "node:crypto";
|
|
4
5
|
import http from "node:http";
|
|
5
6
|
import open from "open";
|
|
6
7
|
import fs from "node:fs";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
import os from "node:os";
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
9
11
|
//#region src/lib/credentials.ts
|
|
10
|
-
const AGENTOPS_DIR = path.join(os.homedir(), ".agentops");
|
|
11
|
-
const CREDENTIALS_PATH = path.join(AGENTOPS_DIR, "credentials.json");
|
|
12
|
-
const CONFIG_PATH = path.join(AGENTOPS_DIR, "config.json");
|
|
12
|
+
const AGENTOPS_DIR$1 = path.join(os.homedir(), ".agentops");
|
|
13
|
+
const CREDENTIALS_PATH = path.join(AGENTOPS_DIR$1, "credentials.json");
|
|
14
|
+
const CONFIG_PATH = path.join(AGENTOPS_DIR$1, "config.json");
|
|
13
15
|
function ensureDir() {
|
|
14
|
-
if (!fs.existsSync(AGENTOPS_DIR)) fs.mkdirSync(AGENTOPS_DIR, {
|
|
16
|
+
if (!fs.existsSync(AGENTOPS_DIR$1)) fs.mkdirSync(AGENTOPS_DIR$1, {
|
|
17
|
+
recursive: true,
|
|
18
|
+
mode: 448
|
|
19
|
+
});
|
|
15
20
|
}
|
|
16
21
|
function saveCredentials(creds) {
|
|
17
22
|
ensureDir();
|
|
18
23
|
fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
|
|
19
24
|
}
|
|
25
|
+
/** Validate that an unknown value has the shape of Credentials */
|
|
26
|
+
function validateCredentials(data) {
|
|
27
|
+
if (!data || typeof data !== "object") return null;
|
|
28
|
+
const d = data;
|
|
29
|
+
if (typeof d.accessToken !== "string" || !d.accessToken) return null;
|
|
30
|
+
const user = d.user;
|
|
31
|
+
if (!user || typeof user.email !== "string" || typeof user.name !== "string") return null;
|
|
32
|
+
const org = d.org;
|
|
33
|
+
if (!org || typeof org.name !== "string" || typeof org.slug !== "string") return null;
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
20
36
|
function loadCredentials() {
|
|
21
37
|
try {
|
|
22
38
|
const raw = fs.readFileSync(CREDENTIALS_PATH, "utf-8");
|
|
23
|
-
return JSON.parse(raw);
|
|
39
|
+
return validateCredentials(JSON.parse(raw));
|
|
24
40
|
} catch {
|
|
25
41
|
return null;
|
|
26
42
|
}
|
|
@@ -32,7 +48,7 @@ function clearCredentials() {
|
|
|
32
48
|
}
|
|
33
49
|
function saveConfig(config) {
|
|
34
50
|
ensureDir();
|
|
35
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
51
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
|
|
36
52
|
}
|
|
37
53
|
function loadConfig() {
|
|
38
54
|
try {
|
|
@@ -68,48 +84,87 @@ function getHeaders() {
|
|
|
68
84
|
}
|
|
69
85
|
//#endregion
|
|
70
86
|
//#region src/commands/login.ts
|
|
71
|
-
const
|
|
87
|
+
const CALLBACK_HOST = "127.0.0.1";
|
|
72
88
|
async function loginCommand(options) {
|
|
73
89
|
const baseUrl = getBaseUrl(options.url);
|
|
74
90
|
console.log(pc.dim(`Server: ${baseUrl}`));
|
|
91
|
+
const expectedState = crypto.randomBytes(20).toString("hex");
|
|
75
92
|
let authRes;
|
|
93
|
+
let callbackPort;
|
|
76
94
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
code,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
const { server, port } = await startCallbackServer();
|
|
96
|
+
callbackPort = port;
|
|
97
|
+
const redirectUri = `http://${CALLBACK_HOST}:${callbackPort}/callback`;
|
|
98
|
+
authRes = await fetch(`${baseUrl}/auth/url?redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(expectedState)}`);
|
|
99
|
+
if (!authRes.ok) {
|
|
100
|
+
server.close();
|
|
101
|
+
console.error(pc.red(`Failed to get auth URL: ${authRes.status} ${await authRes.text()}`));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const { url: authUrl } = await authRes.json();
|
|
105
|
+
console.log(pc.dim(`Listening on http://${CALLBACK_HOST}:${callbackPort}`));
|
|
106
|
+
console.log("Opening browser for authentication...");
|
|
107
|
+
console.log(pc.dim(`If the browser doesn't open, visit:\n${authUrl}`));
|
|
108
|
+
console.log();
|
|
109
|
+
open(authUrl).catch(() => {});
|
|
110
|
+
const { code, state } = await waitForCallback(server, expectedState);
|
|
111
|
+
console.log(pc.dim("Exchanging code for tokens..."));
|
|
112
|
+
const callbackRes = await post("/auth/callback", {
|
|
113
|
+
code,
|
|
114
|
+
state
|
|
115
|
+
}, baseUrl);
|
|
116
|
+
if (!callbackRes.ok) {
|
|
117
|
+
const body = await callbackRes.text();
|
|
118
|
+
console.error(pc.red(`Authentication failed: ${body}`));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const creds = validateCredentials(await callbackRes.json());
|
|
122
|
+
if (!creds) {
|
|
123
|
+
console.error(pc.red("Server returned an unexpected response format."));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
saveCredentials(creds);
|
|
127
|
+
saveConfig({ url: baseUrl });
|
|
128
|
+
console.log();
|
|
129
|
+
console.log(pc.green("✓ Logged in successfully"));
|
|
130
|
+
console.log(` ${pc.bold(creds.user.email)} (${creds.org.name})`);
|
|
131
|
+
console.log(` Role: ${creds.user.role}`);
|
|
132
|
+
console.log();
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
135
|
+
if (message.includes("Cannot reach server") || message.includes("ECONNREFUSED")) {
|
|
136
|
+
console.error(pc.red(`Cannot reach server at ${baseUrl}`));
|
|
137
|
+
console.error(pc.dim("Check the URL and ensure the server is running."));
|
|
138
|
+
} else console.error(pc.red(`Login failed: ${message}`));
|
|
98
139
|
process.exit(1);
|
|
99
140
|
}
|
|
100
|
-
const data = await callbackRes.json();
|
|
101
|
-
saveCredentials(data);
|
|
102
|
-
saveConfig({ url: baseUrl });
|
|
103
|
-
console.log();
|
|
104
|
-
console.log(pc.green("✓ Logged in successfully"));
|
|
105
|
-
console.log(` ${pc.bold(data.user.email)} (${data.org.name})`);
|
|
106
|
-
console.log(` Role: ${data.user.role}`);
|
|
107
|
-
console.log();
|
|
108
141
|
}
|
|
109
|
-
function
|
|
142
|
+
function startCallbackServer() {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const server = http.createServer();
|
|
145
|
+
server.listen(0, CALLBACK_HOST, () => {
|
|
146
|
+
const addr = server.address();
|
|
147
|
+
if (!addr || typeof addr === "string") {
|
|
148
|
+
server.close();
|
|
149
|
+
reject(/* @__PURE__ */ new Error("Failed to get server address"));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
resolve({
|
|
153
|
+
server,
|
|
154
|
+
port: addr.port
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
server.on("error", reject);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function waitForCallback(server, expectedState) {
|
|
110
161
|
return new Promise((resolve, reject) => {
|
|
111
|
-
const
|
|
112
|
-
|
|
162
|
+
const timeout = setTimeout(() => {
|
|
163
|
+
server.close();
|
|
164
|
+
reject(/* @__PURE__ */ new Error("Login timed out after 5 minutes"));
|
|
165
|
+
}, 300 * 1e3);
|
|
166
|
+
server.on("request", (req, res) => {
|
|
167
|
+
const url = new URL(req.url ?? "/", `http://${CALLBACK_HOST}`);
|
|
113
168
|
if (url.pathname !== "/callback") {
|
|
114
169
|
res.writeHead(404);
|
|
115
170
|
res.end("Not found");
|
|
@@ -118,62 +173,182 @@ function captureCallback(authUrl) {
|
|
|
118
173
|
const code = url.searchParams.get("code");
|
|
119
174
|
const state = url.searchParams.get("state");
|
|
120
175
|
if (!code) {
|
|
121
|
-
const error = url.searchParams.get("error") ?? "No code received";
|
|
122
176
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
123
|
-
res.end(
|
|
177
|
+
res.end(resultPage("Authentication Failed", "Check the terminal for details."));
|
|
178
|
+
clearTimeout(timeout);
|
|
179
|
+
server.close();
|
|
180
|
+
const errorMsg = url.searchParams.get("error") ?? "No authorization code received";
|
|
181
|
+
reject(new Error(errorMsg));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (state !== expectedState) {
|
|
185
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
186
|
+
res.end(resultPage("Authentication Failed", "Check the terminal for details."));
|
|
187
|
+
clearTimeout(timeout);
|
|
124
188
|
server.close();
|
|
125
|
-
reject(new Error(
|
|
189
|
+
reject(/* @__PURE__ */ new Error("State mismatch — possible CSRF attack. Try logging in again."));
|
|
126
190
|
return;
|
|
127
191
|
}
|
|
128
192
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
129
|
-
res.end(
|
|
193
|
+
res.end(resultPage("Authenticated", "You can close this tab and return to the terminal."));
|
|
194
|
+
clearTimeout(timeout);
|
|
130
195
|
server.close();
|
|
131
196
|
resolve({
|
|
132
197
|
code,
|
|
133
|
-
state
|
|
198
|
+
state
|
|
134
199
|
});
|
|
135
200
|
});
|
|
136
|
-
server.listen(CALLBACK_PORT, () => {
|
|
137
|
-
console.log(pc.dim(`Listening on http://localhost:${CALLBACK_PORT}`));
|
|
138
|
-
console.log("Opening browser for authentication...");
|
|
139
|
-
console.log();
|
|
140
|
-
open(authUrl);
|
|
141
|
-
});
|
|
142
|
-
server.on("error", (err) => {
|
|
143
|
-
if (err.code === "EADDRINUSE") reject(/* @__PURE__ */ new Error(`Port ${CALLBACK_PORT} is in use. Close the process using it and try again.`));
|
|
144
|
-
else reject(err);
|
|
145
|
-
});
|
|
146
|
-
setTimeout(() => {
|
|
147
|
-
server.close();
|
|
148
|
-
reject(/* @__PURE__ */ new Error("Login timed out after 5 minutes"));
|
|
149
|
-
}, 300 * 1e3);
|
|
150
201
|
});
|
|
151
202
|
}
|
|
152
|
-
|
|
203
|
+
/** Static HTML page — never interpolate user/query data into this */
|
|
204
|
+
function resultPage(title, message) {
|
|
153
205
|
return `<!DOCTYPE html>
|
|
154
206
|
<html><head><title>AgentOps</title></head>
|
|
155
207
|
<body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#0a0a0a;color:#fafafa">
|
|
156
208
|
<div style="text-align:center">
|
|
157
|
-
<h1
|
|
158
|
-
<p
|
|
209
|
+
<h1>${title}</h1>
|
|
210
|
+
<p>${message}</p>
|
|
159
211
|
</div>
|
|
160
212
|
</body></html>`;
|
|
161
213
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/commands/setup.ts
|
|
216
|
+
const SUPPORTED_EDITORS = [
|
|
217
|
+
"cursor",
|
|
218
|
+
"claude",
|
|
219
|
+
"codex"
|
|
220
|
+
];
|
|
221
|
+
const AGENTOPS_DIR = path.join(os.homedir(), ".agentops");
|
|
222
|
+
const SCRIPTS_DIR = path.join(AGENTOPS_DIR, "scripts");
|
|
223
|
+
async function setupCommand(options) {
|
|
224
|
+
const creds = loadCredentials();
|
|
225
|
+
if (!creds) {
|
|
226
|
+
console.error(pc.red("Not logged in. Run: agentops login"));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const editor = options.editor?.toLowerCase();
|
|
230
|
+
if (!editor || !SUPPORTED_EDITORS.includes(editor)) {
|
|
231
|
+
console.error(pc.red(`Please specify an editor: --editor ${SUPPORTED_EDITORS.join(" | ")}`));
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
const baseUrl = getBaseUrl(options.url);
|
|
235
|
+
console.log(pc.dim(`Server: ${baseUrl}`));
|
|
236
|
+
console.log(`Logged in as ${pc.bold(creds.user.email)} (${creds.org.name})`);
|
|
237
|
+
console.log(`Setting up for ${pc.bold(editor)}...`);
|
|
238
|
+
console.log();
|
|
239
|
+
copyScripts();
|
|
240
|
+
fs.writeFileSync(path.join(AGENTOPS_DIR, "editor.json"), JSON.stringify({ editor }, null, 2), { mode: 384 });
|
|
241
|
+
switch (editor) {
|
|
242
|
+
case "cursor":
|
|
243
|
+
setupCursor();
|
|
244
|
+
break;
|
|
245
|
+
case "claude":
|
|
246
|
+
setupClaude();
|
|
247
|
+
break;
|
|
248
|
+
case "codex":
|
|
249
|
+
setupCodex();
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
console.log(pc.dim("Running first sync..."));
|
|
253
|
+
try {
|
|
254
|
+
execFileSync("node", [path.join(SCRIPTS_DIR, "sync.mjs")], {
|
|
255
|
+
stdio: [
|
|
256
|
+
"pipe",
|
|
257
|
+
"pipe",
|
|
258
|
+
"inherit"
|
|
259
|
+
],
|
|
260
|
+
env: {
|
|
261
|
+
...process.env,
|
|
262
|
+
AGENTOPS_API_URL: baseUrl,
|
|
263
|
+
AGENTOPS_EDITOR: editor
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
console.log(pc.green(" Sync complete"));
|
|
267
|
+
} catch {
|
|
268
|
+
console.log(pc.yellow(" First sync failed — will retry on next session start"));
|
|
269
|
+
}
|
|
270
|
+
console.log();
|
|
271
|
+
console.log(pc.green("Setup complete."));
|
|
272
|
+
console.log(pc.dim("Skills will sync automatically on every session start."));
|
|
273
|
+
}
|
|
274
|
+
function copyScripts() {
|
|
275
|
+
fs.mkdirSync(SCRIPTS_DIR, {
|
|
276
|
+
recursive: true,
|
|
277
|
+
mode: 448
|
|
278
|
+
});
|
|
279
|
+
const source = [path.resolve(import.meta.dirname, "..", "..", "scripts", "sync.mjs"), path.resolve(import.meta.dirname, "..", "scripts", "sync.mjs")].find((p) => fs.existsSync(p));
|
|
280
|
+
if (!source) {
|
|
281
|
+
console.error(pc.red("Could not find sync.mjs script"));
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
fs.copyFileSync(source, path.join(SCRIPTS_DIR, "sync.mjs"));
|
|
285
|
+
console.log(pc.green(" Sync scripts installed to ~/.agentops/scripts/"));
|
|
286
|
+
}
|
|
287
|
+
function setupCursor() {
|
|
288
|
+
const cursorDir = path.join(os.homedir(), ".cursor");
|
|
289
|
+
const hooksPath = path.join(cursorDir, "hooks.json");
|
|
290
|
+
let hooks = {
|
|
291
|
+
version: 1,
|
|
292
|
+
hooks: {}
|
|
293
|
+
};
|
|
294
|
+
if (fs.existsSync(hooksPath)) try {
|
|
295
|
+
hooks = JSON.parse(fs.readFileSync(hooksPath, "utf-8"));
|
|
296
|
+
} catch {}
|
|
297
|
+
const hooksObj = hooks.hooks ?? {};
|
|
298
|
+
const syncCommand = `node "${path.join(SCRIPTS_DIR, "sync.mjs")}"`;
|
|
299
|
+
const filtered = (hooksObj.sessionStart ?? []).filter((h) => !h.command?.includes("agentops"));
|
|
300
|
+
filtered.push({
|
|
301
|
+
command: syncCommand,
|
|
302
|
+
timeout: 30
|
|
303
|
+
});
|
|
304
|
+
hooksObj.sessionStart = filtered;
|
|
305
|
+
hooks.hooks = hooksObj;
|
|
306
|
+
if (!hooks.version) hooks.version = 1;
|
|
307
|
+
fs.writeFileSync(hooksPath, JSON.stringify(hooks, null, 2));
|
|
308
|
+
fs.mkdirSync(path.join(cursorDir, "commands"), { recursive: true });
|
|
309
|
+
fs.mkdirSync(path.join(cursorDir, "rules"), { recursive: true });
|
|
310
|
+
console.log(pc.green(" Cursor hooks.json configured"));
|
|
311
|
+
}
|
|
312
|
+
function setupClaude() {
|
|
313
|
+
try {
|
|
314
|
+
execFileSync("claude", [
|
|
315
|
+
"plugin",
|
|
316
|
+
"add",
|
|
317
|
+
"https://github.com/bonnard-data/agentops-plugin"
|
|
318
|
+
], { stdio: "inherit" });
|
|
319
|
+
console.log(pc.green(" Claude Code plugin installed"));
|
|
320
|
+
} catch {
|
|
321
|
+
console.log(pc.yellow(" Claude Code plugin install failed (may already be installed)"));
|
|
322
|
+
console.log(pc.dim(" Install manually: claude plugin add https://github.com/bonnard-data/agentops-plugin"));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function setupCodex() {
|
|
326
|
+
const codexDir = path.join(os.homedir(), ".codex");
|
|
327
|
+
const hooksPath = path.join(codexDir, "hooks.json");
|
|
328
|
+
let hooks = {};
|
|
329
|
+
if (fs.existsSync(hooksPath)) try {
|
|
330
|
+
hooks = JSON.parse(fs.readFileSync(hooksPath, "utf-8"));
|
|
331
|
+
} catch {}
|
|
332
|
+
const hooksObj = hooks.hooks ?? {};
|
|
333
|
+
const syncCommand = `node "${path.join(SCRIPTS_DIR, "sync.mjs")}"`;
|
|
334
|
+
const filtered = (hooksObj.SessionStart ?? []).filter((h) => !h.command?.includes("agentops"));
|
|
335
|
+
filtered.push({
|
|
336
|
+
command: syncCommand,
|
|
337
|
+
timeout: 30
|
|
338
|
+
});
|
|
339
|
+
hooksObj.SessionStart = filtered;
|
|
340
|
+
hooks.hooks = hooksObj;
|
|
341
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
342
|
+
fs.writeFileSync(hooksPath, JSON.stringify(hooks, null, 2));
|
|
343
|
+
fs.mkdirSync(path.join(os.homedir(), ".agents", "skills"), { recursive: true });
|
|
344
|
+
console.log(pc.green(" Codex hooks.json configured"));
|
|
171
345
|
}
|
|
172
346
|
//#endregion
|
|
173
347
|
//#region src/bin/agentops.ts
|
|
174
348
|
const program = new Command();
|
|
175
349
|
program.name("agentops").description("AgentOps CLI — setup and manage your AI agent skills").version("0.1.0");
|
|
176
350
|
program.command("login").description("Authenticate with AgentOps via your browser").option("--url <url>", "AgentOps server URL").action(loginCommand);
|
|
351
|
+
program.command("setup").description("Configure an editor for AgentOps skill sync").requiredOption("--editor <editor>", "Editor to configure (cursor, claude, codex)").option("--url <url>", "AgentOps server URL").action(setupCommand);
|
|
177
352
|
program.command("logout").description("Clear saved credentials").action(() => {
|
|
178
353
|
clearCredentials();
|
|
179
354
|
console.log(pc.green("✓ Logged out"));
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bonnard/agentops",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentops": "./dist/bin/agentops.mjs"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"dist"
|
|
9
|
+
"dist",
|
|
10
|
+
"scripts"
|
|
10
11
|
],
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|
|
@@ -22,17 +23,17 @@
|
|
|
22
23
|
"lint": "eslint ."
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"commander": "
|
|
26
|
-
"open": "
|
|
27
|
-
"picocolors": "
|
|
26
|
+
"commander": "14.0.3",
|
|
27
|
+
"open": "11.0.0",
|
|
28
|
+
"picocolors": "1.1.1"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@eslint/js": "
|
|
31
|
-
"@types/node": "
|
|
32
|
-
"eslint": "
|
|
33
|
-
"tsdown": "
|
|
34
|
-
"tsx": "
|
|
35
|
-
"typescript": "
|
|
36
|
-
"typescript-eslint": "
|
|
31
|
+
"@eslint/js": "10.0.1",
|
|
32
|
+
"@types/node": "22.19.17",
|
|
33
|
+
"eslint": "10.2.0",
|
|
34
|
+
"tsdown": "0.21.7",
|
|
35
|
+
"tsx": "4.21.0",
|
|
36
|
+
"typescript": "6.0.2",
|
|
37
|
+
"typescript-eslint": "8.58.0"
|
|
37
38
|
}
|
|
38
39
|
}
|