argus-media-cli 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +80 -0
  2. package/dist/index.js +326 -139
  3. package/package.json +23 -5
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # argus-media-cli
2
+
3
+ CLI for [Argus](https://argus.build) — AI-native media asset management.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g argus-media-cli
9
+ ```
10
+
11
+ Or run directly:
12
+
13
+ ```bash
14
+ npx argus-media-cli upload photo.jpg
15
+ ```
16
+
17
+ ## Setup
18
+
19
+ ```bash
20
+ argus login
21
+ ```
22
+
23
+ Or set your API key as an environment variable:
24
+
25
+ ```bash
26
+ export ARGUS_API_KEY=ak_your_key_here
27
+ ```
28
+
29
+ ## Commands
30
+
31
+ ### Upload
32
+
33
+ ```bash
34
+ argus upload photo.jpg
35
+ argus upload hero.png --project campaign-q2 --tags hero,banner
36
+ ```
37
+
38
+ ### Search
39
+
40
+ ```bash
41
+ argus search "sunset landscape"
42
+ argus search --project website --status ready --json
43
+ ```
44
+
45
+ ### List
46
+
47
+ ```bash
48
+ argus list
49
+ argus list --project website --limit 20
50
+ ```
51
+
52
+ ### Get
53
+
54
+ ```bash
55
+ argus get <asset-id>
56
+ argus get abc12345 --json
57
+ ```
58
+
59
+ ### Login
60
+
61
+ ```bash
62
+ argus login # Interactive prompt
63
+ argus login --key ak_xxx # Non-interactive
64
+ argus login --url http://localhost:8787 # Custom API URL
65
+ ```
66
+
67
+ ## Options
68
+
69
+ All commands support `--json` for structured JSON output.
70
+
71
+ ## Environment Variables
72
+
73
+ | Variable | Description |
74
+ |---|---|
75
+ | `ARGUS_API_KEY` | API key (overrides saved config) |
76
+ | `ARGUS_BASE_URL` | API base URL (default: `https://argus.build`) |
77
+
78
+ ## License
79
+
80
+ MIT
package/dist/index.js CHANGED
@@ -1,180 +1,367 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
3
- import { join } from "path";
4
- import { homedir } from "os";
5
- const BASE_URL = process.env.ARGUS_BASE_URL || "https://argus.build";
2
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
3
+ import { basename, extname, join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import * as readline from "node:readline";
6
+ // ─── Config ────────────────────────────────────────────────────────────────
6
7
  const CONFIG_DIR = join(homedir(), ".argus");
7
8
  const CONFIG_FILE = join(CONFIG_DIR, "config.json");
8
- // ── Config ────────────────────────────────────────────────────────────────────
9
- function loadConfig() {
9
+ async function loadConfig() {
10
10
  try {
11
- return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
11
+ const data = await readFile(CONFIG_FILE, "utf-8");
12
+ return JSON.parse(data);
12
13
  }
13
14
  catch {
14
15
  return {};
15
16
  }
16
17
  }
17
- function saveConfig(config) {
18
- mkdirSync(CONFIG_DIR, { recursive: true });
19
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
18
+ async function saveConfig(config) {
19
+ await mkdir(CONFIG_DIR, { recursive: true });
20
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
20
21
  }
21
- function getApiKey() {
22
- const key = process.env.ARGUS_API_KEY || loadConfig().apiKey;
22
+ // ─── API Client ────────────────────────────────────────────────────────────
23
+ function getBaseUrl(config) {
24
+ return process.env.ARGUS_BASE_URL || config.baseUrl || "https://argus.build";
25
+ }
26
+ function getApiKey(config) {
27
+ const key = process.env.ARGUS_API_KEY || config.apiKey;
23
28
  if (!key) {
24
- console.error("No API key found. Run: argus login <api-key>");
25
- console.error("Or set ARGUS_API_KEY in your environment.");
29
+ console.error("Error: No API key configured.");
30
+ console.error("Run `argus login` to authenticate, or set ARGUS_API_KEY.");
26
31
  process.exit(1);
27
32
  }
28
33
  return key;
29
34
  }
30
- // ── HTTP ──────────────────────────────────────────────────────────────────────
31
- async function api(method, path, body) {
32
- const res = await fetch(`${BASE_URL}${path}`, {
35
+ async function apiRequest(config, method, path, body) {
36
+ const url = `${getBaseUrl(config)}${path}`;
37
+ const apiKey = getApiKey(config);
38
+ const opts = {
33
39
  method,
34
40
  headers: {
35
- Authorization: `Bearer ${getApiKey()}`,
41
+ Authorization: `Bearer ${apiKey}`,
36
42
  ...(body ? { "Content-Type": "application/json" } : {}),
37
43
  },
38
44
  ...(body ? { body: JSON.stringify(body) } : {}),
39
- });
40
- const data = await res.json().catch(() => ({}));
45
+ };
46
+ const res = await fetch(url, opts);
41
47
  if (!res.ok) {
42
- const err = data.error || res.statusText;
43
- console.error(`Error: ${err}`);
48
+ const text = await res.text();
49
+ throw new Error(`API ${method} ${path} failed (${res.status}): ${text}`);
50
+ }
51
+ return res.json();
52
+ }
53
+ // ─── MIME Types ────────────────────────────────────────────────────────────
54
+ const MIME_TYPES = {
55
+ ".jpg": "image/jpeg",
56
+ ".jpeg": "image/jpeg",
57
+ ".png": "image/png",
58
+ ".gif": "image/gif",
59
+ ".webp": "image/webp",
60
+ ".svg": "image/svg+xml",
61
+ ".mp4": "video/mp4",
62
+ ".mov": "video/quicktime",
63
+ ".webm": "video/webm",
64
+ ".mp3": "audio/mpeg",
65
+ ".wav": "audio/wav",
66
+ ".pdf": "application/pdf",
67
+ };
68
+ // ─── Formatting ────────────────────────────────────────────────────────────
69
+ function formatBytes(bytes) {
70
+ if (bytes < 1024)
71
+ return `${bytes} B`;
72
+ if (bytes < 1024 * 1024)
73
+ return `${(bytes / 1024).toFixed(1)} KB`;
74
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
75
+ }
76
+ function formatAssetRow(a) {
77
+ const status = a.status === "ready" ? "\x1b[32m●\x1b[0m" : a.status === "pending" ? "\x1b[33m●\x1b[0m" : "\x1b[31m●\x1b[0m";
78
+ const size = formatBytes(a.size || 0);
79
+ const project = a.project ? ` [${a.project}]` : "";
80
+ const tags = a.tags?.length ? ` (${a.tags.join(", ")})` : "";
81
+ return `${status} ${a.id.slice(0, 8)} ${a.filename} ${size}${project}${tags}`;
82
+ }
83
+ function formatAssetDetail(a) {
84
+ const lines = [
85
+ `\x1b[1m${a.filename}\x1b[0m`,
86
+ ` ID: ${a.id}`,
87
+ ` Status: ${a.status}`,
88
+ ` Type: ${a.mimeType}`,
89
+ ` Size: ${formatBytes(a.size || 0)}`,
90
+ ];
91
+ if (a.project)
92
+ lines.push(` Project: ${a.project}`);
93
+ if (a.url)
94
+ lines.push(` URL: ${a.url}`);
95
+ if (a.tags?.length)
96
+ lines.push(` Tags: ${a.tags.join(", ")}`);
97
+ if (a.uploadedBy)
98
+ lines.push(` Uploader: ${a.uploadedBy}`);
99
+ if (a.uploadedAt)
100
+ lines.push(` Uploaded: ${a.uploadedAt}`);
101
+ if (a.analysis) {
102
+ lines.push(` \x1b[1mAnalysis:\x1b[0m`);
103
+ if (a.analysis.description)
104
+ lines.push(` ${a.analysis.description}`);
105
+ if (a.analysis.mood)
106
+ lines.push(` Mood: ${a.analysis.mood}`);
107
+ if (a.analysis.dominantColors?.length) {
108
+ const colors = a.analysis.dominantColors.map((c) => `${c.name} (${c.hex})`).join(", ");
109
+ lines.push(` Colors: ${colors}`);
110
+ }
111
+ if (a.analysis.useCases?.length)
112
+ lines.push(` Uses: ${a.analysis.useCases.join(", ")}`);
113
+ if (a.analysis.tags?.length)
114
+ lines.push(` Tags: ${a.analysis.tags.join(", ")}`);
115
+ }
116
+ return lines.join("\n");
117
+ }
118
+ // ─── Commands ──────────────────────────────────────────────────────────────
119
+ async function cmdLogin(args, flags) {
120
+ const config = await loadConfig();
121
+ if (flags.key) {
122
+ config.apiKey = flags.key;
123
+ await saveConfig(config);
124
+ console.log("API key saved to ~/.argus/config.json");
125
+ return;
126
+ }
127
+ if (flags.url) {
128
+ config.baseUrl = flags.url;
129
+ await saveConfig(config);
130
+ console.log(`Base URL set to ${flags.url}`);
131
+ return;
132
+ }
133
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
134
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
135
+ console.log("Argus CLI Login");
136
+ console.log("Get your API key at https://argus.build/ui\n");
137
+ const key = await ask("API key: ");
138
+ rl.close();
139
+ if (!key.trim()) {
140
+ console.error("No key provided.");
141
+ process.exit(1);
142
+ }
143
+ config.apiKey = key.trim();
144
+ await saveConfig(config);
145
+ // Verify the key works
146
+ try {
147
+ const res = await apiRequest(config, "GET", "/usage");
148
+ console.log(`\nAuthenticated! Workspace tier: ${res.tier}`);
149
+ console.log(`Assets: ${res.assets?.used ?? 0}/${res.assets?.limit ?? "unlimited"}`);
150
+ }
151
+ catch (e) {
152
+ console.error(`\nWarning: Could not verify key — ${e.message}`);
153
+ console.log("Key saved anyway. Check it with `argus list`.");
154
+ }
155
+ }
156
+ async function cmdUpload(args, flags) {
157
+ const filePath = args[0];
158
+ if (!filePath) {
159
+ console.error("Usage: argus upload <file> [--project NAME] [--tags a,b,c]");
44
160
  process.exit(1);
45
161
  }
46
- return data;
162
+ const config = await loadConfig();
163
+ const apiKey = getApiKey(config);
164
+ const baseUrl = getBaseUrl(config);
165
+ // Read file
166
+ const fileData = await readFile(filePath);
167
+ const fileName = basename(filePath);
168
+ const ext = extname(fileName).toLowerCase();
169
+ const mimeType = MIME_TYPES[ext] || "application/octet-stream";
170
+ // Build multipart form data
171
+ const blob = new Blob([fileData], { type: mimeType });
172
+ const form = new FormData();
173
+ form.append("file", blob, fileName);
174
+ if (flags.project)
175
+ form.append("project", flags.project);
176
+ if (flags.tags)
177
+ form.append("tags", flags.tags);
178
+ const res = await fetch(`${baseUrl}/assets/upload`, {
179
+ method: "POST",
180
+ headers: { Authorization: `Bearer ${apiKey}` },
181
+ body: form,
182
+ });
183
+ if (!res.ok) {
184
+ const text = await res.text();
185
+ throw new Error(`Upload failed (${res.status}): ${text}`);
186
+ }
187
+ const data = (await res.json());
188
+ const asset = data.asset;
189
+ if (flags.json) {
190
+ console.log(JSON.stringify(data, null, 2));
191
+ }
192
+ else {
193
+ console.log(`Uploaded: ${asset.filename} (${asset.id})`);
194
+ console.log(`Status: ${asset.status}`);
195
+ if (asset.url)
196
+ console.log(`URL: ${asset.url}`);
197
+ }
47
198
  }
48
- // ── Output ────────────────────────────────────────────────────────────────────
49
- function output(data, json) {
50
- if (json) {
199
+ async function cmdSearch(args, flags) {
200
+ const query = args.join(" ");
201
+ const config = await loadConfig();
202
+ const params = new URLSearchParams();
203
+ if (query)
204
+ params.set("q", query);
205
+ if (flags.project)
206
+ params.set("project", flags.project);
207
+ if (flags.status)
208
+ params.set("status", flags.status);
209
+ if (flags.tags)
210
+ params.set("tags", flags.tags);
211
+ if (flags.limit)
212
+ params.set("limit", flags.limit);
213
+ if (flags.offset)
214
+ params.set("offset", flags.offset);
215
+ const qs = params.toString();
216
+ const data = (await apiRequest(config, "GET", `/assets${qs ? `?${qs}` : ""}`));
217
+ if (flags.json) {
51
218
  console.log(JSON.stringify(data, null, 2));
52
219
  return;
53
220
  }
54
- const asset = data.asset;
55
- const assets = data.assets;
56
- if (asset)
57
- printAsset(asset);
58
- else if (assets)
59
- assets.forEach(printAsset);
60
- else
221
+ if (!data.assets?.length) {
222
+ console.log("No assets found.");
223
+ return;
224
+ }
225
+ console.log(`${data.total} asset${data.total === 1 ? "" : "s"} found:\n`);
226
+ for (const a of data.assets) {
227
+ console.log(formatAssetRow(a));
228
+ }
229
+ }
230
+ async function cmdList(_args, flags) {
231
+ return cmdSearch([], flags);
232
+ }
233
+ async function cmdGet(args, flags) {
234
+ const id = args[0];
235
+ if (!id) {
236
+ console.error("Usage: argus get <asset-id>");
237
+ process.exit(1);
238
+ }
239
+ const config = await loadConfig();
240
+ const data = (await apiRequest(config, "GET", `/assets/${id}`));
241
+ if (flags.json) {
61
242
  console.log(JSON.stringify(data, null, 2));
243
+ return;
244
+ }
245
+ console.log(formatAssetDetail(data.asset));
62
246
  }
63
- function printAsset(a) {
64
- const analysis = a.analysis;
65
- console.log(`\n${a.id}`);
66
- console.log(` File: ${a.filename}`);
67
- console.log(` URL: ${a.url}`);
68
- console.log(` Status: ${a.status}`);
69
- if (a.project)
70
- console.log(` Project: ${a.project}`);
71
- if (a.tags && Array.isArray(a.tags) && a.tags.length)
72
- console.log(` Tags: ${a.tags.join(", ")}`);
73
- if (analysis?.description)
74
- console.log(` Desc: ${analysis.description}`);
75
- if (analysis?.mood)
76
- console.log(` Mood: ${analysis.mood}`);
77
- }
78
- // ── Commands ──────────────────────────────────────────────────────────────────
79
- const args = process.argv.slice(2);
80
- const jsonFlag = args.includes("--json");
81
- const filteredArgs = args.filter((a) => a !== "--json");
82
- const [command, ...rest] = filteredArgs;
83
- async function main() {
84
- switch (command) {
85
- case "login": {
86
- const key = rest[0];
87
- if (!key) {
88
- console.error("Usage: argus login <api-key>");
89
- process.exit(1);
90
- }
91
- saveConfig({ ...loadConfig(), apiKey: key });
92
- console.log("API key saved to ~/.argus/config.json");
93
- break;
94
- }
95
- case "upload": {
96
- const file = rest[0];
97
- const project = rest[1];
98
- if (!file || !existsSync(file)) {
99
- console.error("Usage: argus upload <file> [project]");
100
- process.exit(1);
101
- }
102
- const { createReadStream, statSync } = await import("fs");
103
- const { basename } = await import("path");
104
- const fd = new FormData();
105
- const bytes = readFileSync(file);
106
- fd.append("file", new Blob([bytes]), basename(file));
107
- if (project)
108
- fd.append("project", project);
109
- const res = await fetch(`${BASE_URL}/assets/upload`, {
110
- method: "POST",
111
- headers: { Authorization: `Bearer ${getApiKey()}` },
112
- body: fd,
113
- });
114
- const data = await res.json().catch(() => ({}));
115
- if (!res.ok) {
116
- console.error(`Error: ${data.error || res.statusText}`);
117
- process.exit(1);
118
- }
119
- output(data, jsonFlag);
120
- break;
247
+ async function cmdUsage(_args, flags) {
248
+ const config = await loadConfig();
249
+ const data = (await apiRequest(config, "GET", "/usage"));
250
+ if (flags.json) {
251
+ console.log(JSON.stringify(data, null, 2));
252
+ return;
253
+ }
254
+ console.log(`\x1b[1mWorkspace Usage\x1b[0m`);
255
+ console.log(` Tier: ${data.tier}`);
256
+ console.log(` Assets: ${data.assets?.used ?? 0} / ${data.assets?.limit ?? "unlimited"}`);
257
+ if (data.credits != null) {
258
+ console.log(` Credits: ${data.credits?.used ?? 0} / ${data.credits?.limit ?? "unlimited"}`);
259
+ }
260
+ if (data.storage != null) {
261
+ console.log(` Storage: ${data.storage?.used != null ? formatBytes(data.storage.used) : "0 B"} / ${data.storage?.limit != null ? formatBytes(data.storage.limit) : "unlimited"}`);
262
+ }
263
+ }
264
+ function parseArgs(argv) {
265
+ const command = argv[0] || "help";
266
+ const flags = {};
267
+ const args = [];
268
+ for (let i = 1; i < argv.length; i++) {
269
+ const arg = argv[i];
270
+ if (arg === "--json") {
271
+ flags.json = true;
121
272
  }
122
- case "search": {
123
- const query = rest.join(" ");
124
- if (!query) {
125
- console.error("Usage: argus search <query>");
126
- process.exit(1);
273
+ else if (arg.startsWith("--")) {
274
+ const key = arg.slice(2);
275
+ const next = argv[i + 1];
276
+ if (next && !next.startsWith("--")) {
277
+ flags[key] = next;
278
+ i++;
127
279
  }
128
- const data = await api("GET", `/assets?q=${encodeURIComponent(query)}`);
129
- output(data, jsonFlag);
130
- break;
131
- }
132
- case "list": {
133
- const project = rest[0];
134
- const qs = project ? `?project=${encodeURIComponent(project)}` : "";
135
- const data = await api("GET", `/assets${qs}`);
136
- output(data, jsonFlag);
137
- break;
138
- }
139
- case "get": {
140
- const id = rest[0];
141
- if (!id) {
142
- console.error("Usage: argus get <id>");
143
- process.exit(1);
280
+ else {
281
+ flags[key] = true;
144
282
  }
145
- const data = await api("GET", `/assets/${id}`);
146
- output(data, jsonFlag);
147
- break;
148
283
  }
149
- case "usage": {
150
- const data = await api("GET", "/usage");
151
- output(data, jsonFlag);
152
- break;
284
+ else {
285
+ args.push(arg);
153
286
  }
154
- default:
155
- console.log(`argus <command> [options]
287
+ }
288
+ return { command, args, flags };
289
+ }
290
+ function printHelp() {
291
+ console.log(`\x1b[1margus\x1b[0m — AI-native media asset management
292
+
293
+ \x1b[1mUsage:\x1b[0m
294
+ argus <command> [options]
295
+
296
+ \x1b[1mCommands:\x1b[0m
297
+ login Authenticate with Argus
298
+ upload <file> Upload a media file
299
+ search <query> Search assets by description, tags, mood
300
+ list List all assets
301
+ get <id> Get asset details and analysis
302
+ usage Show workspace usage and limits
303
+
304
+ \x1b[1mGlobal Options:\x1b[0m
305
+ --json Output raw JSON
156
306
 
157
- Commands:
158
- login <api-key> Save API key to ~/.argus/config.json
159
- upload <file> [project] Upload an image
160
- search <query> Search assets by keyword
161
- list [project] List assets
162
- get <id> Get asset by ID
163
- usage Show credit and asset usage
307
+ \x1b[1mUpload Options:\x1b[0m
308
+ --project NAME Assign to a project
309
+ --tags a,b,c Comma-separated tags
164
310
 
165
- Options:
166
- --json Output raw JSON
311
+ \x1b[1mSearch/List Options:\x1b[0m
312
+ --project NAME Filter by project
313
+ --status STATUS Filter by status (pending, ready, error)
314
+ --tags a,b,c Filter by tags
315
+ --limit N Max results (default 50)
316
+ --offset N Pagination offset
167
317
 
168
- Environment:
169
- ARGUS_API_KEY API key (overrides saved config)
170
- ARGUS_BASE_URL API base URL (default: https://argus.build)
318
+ \x1b[1mLogin Options:\x1b[0m
319
+ --key KEY Set API key non-interactively
320
+ --url URL Set custom API base URL
171
321
 
172
- Get an API key at https://argus.build/ui/settings
322
+ \x1b[1mEnvironment:\x1b[0m
323
+ ARGUS_API_KEY API key (overrides saved config)
324
+ ARGUS_BASE_URL API base URL (default: https://argus.build)
325
+
326
+ \x1b[1mExamples:\x1b[0m
327
+ argus login
328
+ argus upload photo.jpg --project campaign-q2 --tags hero,banner
329
+ argus search "sunset landscape" --json
330
+ argus list --project website --limit 20
331
+ argus get abc12345-def6-7890-abcd-ef1234567890
173
332
  `);
174
- process.exit(command ? 1 : 0);
333
+ }
334
+ // ─── Main ──────────────────────────────────────────────────────────────────
335
+ const COMMANDS = {
336
+ login: cmdLogin,
337
+ upload: cmdUpload,
338
+ search: cmdSearch,
339
+ list: cmdList,
340
+ get: cmdGet,
341
+ usage: cmdUsage,
342
+ };
343
+ async function main() {
344
+ const { command, args, flags } = parseArgs(process.argv.slice(2));
345
+ if (command === "help" || command === "--help" || command === "-h") {
346
+ printHelp();
347
+ return;
348
+ }
349
+ if (command === "version" || command === "--version" || command === "-v") {
350
+ console.log("argus-media-cli 0.1.1");
351
+ return;
352
+ }
353
+ const handler = COMMANDS[command];
354
+ if (!handler) {
355
+ console.error(`Unknown command: ${command}`);
356
+ console.error('Run "argus help" for usage.');
357
+ process.exit(1);
358
+ }
359
+ try {
360
+ await handler(args, flags);
361
+ }
362
+ catch (err) {
363
+ console.error(`Error: ${err.message}`);
364
+ process.exit(1);
175
365
  }
176
366
  }
177
- main().catch((err) => {
178
- console.error(err.message || err);
179
- process.exit(1);
180
- });
367
+ main();
package/package.json CHANGED
@@ -1,17 +1,33 @@
1
1
  {
2
2
  "name": "argus-media-cli",
3
- "version": "0.1.0",
4
- "description": "CLI for Argus — AI-powered media catalog. Upload, search, and manage media assets from the terminal.",
5
- "keywords": ["argus", "media", "assets", "cli", "ai", "mcp"],
3
+ "version": "0.1.1",
4
+ "description": "CLI for Argus — AI-native media asset management. Upload, search, analyze, and manage media assets from the terminal.",
5
+ "keywords": [
6
+ "argus",
7
+ "media",
8
+ "assets",
9
+ "ai",
10
+ "cli",
11
+ "upload",
12
+ "image",
13
+ "video"
14
+ ],
6
15
  "license": "MIT",
7
16
  "author": "Brookfield Digital",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/brookfield-digital/argus"
20
+ },
8
21
  "homepage": "https://argus.build",
9
22
  "type": "module",
10
23
  "bin": {
11
24
  "argus": "./dist/index.js"
12
25
  },
13
26
  "main": "./dist/index.js",
14
- "files": ["dist", "README.md"],
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
15
31
  "scripts": {
16
32
  "build": "tsc",
17
33
  "prepublishOnly": "npm run build"
@@ -20,5 +36,7 @@
20
36
  "@types/node": "^20.0.0",
21
37
  "typescript": "^5.4.0"
22
38
  },
23
- "engines": { "node": ">=18" }
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
24
42
  }