@mearl/setup 2.15.3 → 2.16.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.
package/README.md CHANGED
@@ -31,18 +31,22 @@ mearl yuque read "https://aliyuque.antfin.com/group/book/doc"
31
31
 
32
32
  安装参数透传给 `npx -y ali-skills add`,末尾统一追加 `--global --install-mode symlink`;不接受会截断后置选项的 `--` 参数分隔符,
33
33
  确保 skill 存放在 `~/.agents/skills` 并共享给所选 agent。安装日志实时显示,保留交互;
34
- setup 从 `Installed … skill(s)` 成功区块提取路径,仅注册本次安装且包含 `scripts/main.mjs` 的 skill。
34
+ setup 从 `Installed … skill(s)` 成功区块提取路径,仅注册本次安装且包含 `scripts/main.mjs` 或 `scripts/main.py` 的 skill;两者同时存在时使用 JavaScript 入口。
35
35
  普通 skill 正常安装。输出格式无法识别时提示手动注册,不扫描其他 skill 或猜测安装结果。
36
36
 
37
- 默认 `install` 和 `update` 完成后,setup 会用最多 5 秒的 `npm ping` 检查阿里内网 registry。
38
- 可访问时通过上述 Ali Skills 链路依次自动安装并注册 `buc-request` 与 `tdbank-account`;该 registry 不用于下载插件。
39
- 探测不可用或插件安装失败不会破坏 Mearl 主环境的安装;`--skip-skill` 和
40
- `--target-line` 安装不执行自动插件步骤。
37
+ 默认 `install` 和 `update` 会用最多 5 秒的 `npm ping` 检查阿里内网 registry。
38
+ registry 可访问时通过 Ali Skills 批量处理 `mearl`、`buc-request` 与 `tdbank-account`:安装使用一次
39
+ `add` 并注册两个插件,更新已跟踪项使用一次 `update`;卸载时使用一次 `remove` 批量移除 setup 管理的
40
+ 三项 Skill。
41
+ 更新发现某个官方插件缺少注册时,会在批量更新已跟踪项后通过一次 `add` 补装并恢复注册。
42
+ 该 registry 不用于下载插件。
43
+ 探测不可用时按普通外网流程只处理 Mearl Skill;内网批量命令失败时 setup 失败,不再逐项重试。
44
+ `--skip-skill` 和 `--target-line` 安装不执行自动插件步骤。
41
45
 
42
46
  Mearl client 内置官方插件名称与安装来源目录。CLI、SDK、MCP 或另一个插件首次调用尚未注册的官方插件时,会通过内部幂等流程安装并重试一次;未知名称不会触发安装。
43
47
 
44
- 自动注册默认使用安装目录名;入口可用 `export const name = 'yuque'` 指定简短命令名。
45
- 重装会刷新同名绑定。每个入口必须默认导出 `register(app)`;完整协议见
48
+ 自动注册默认使用安装目录名;入口可声明 `name` 指定简短命令名。
49
+ 重装会刷新同名绑定。JavaScript 入口必须默认导出 `register(app)`,Python 入口必须导出 `register(app)` 函数;完整协议见
46
50
  [Skill 插件规范](../skills/mearl-skill-creator/references/cli-plugin.md)。
47
51
 
48
52
  已通过其他渠道分发的 skill 或自定义入口,可以直接传入路径注册:
@@ -52,7 +56,7 @@ npx @mearl/setup plugin yuque /absolute/path/to/yuque-doc-fetch
52
56
  npx @mearl/setup plugin custom ./skills/custom/entry.mjs
53
57
  ```
54
58
 
55
- path 接受目录或具体文件;目录默认读取 `scripts/main.mjs`,相对路径基于调用工作目录解析。
59
+ path 接受目录或具体文件;目录默认读取 `scripts/main.mjs` 或 `scripts/main.py`,相对路径基于调用工作目录解析。Python 插件要求 `python3 >= 3.9`,可通过 `MEARL_PYTHON` 指定解释器路径;setup 只验证入口可加载,不安装 Python,也不执行 `pip install`。
56
60
  命令检查入口注册函数,然后保存绝对入口路径,同名注册更新绑定。
57
61
  两种注册流程都会检查本机 `@mearl/client` 和 `@mearl/native-host`:缺失或低于当前 setup 版本时,
58
62
  复用已有包管理器安装 setup 的对应版本,更新后再次验证;更高版本保持不动。
@@ -1,10 +1,13 @@
1
- import { findPluginRegistration } from '@mearl/client/plugin';
2
- import { installPlugins, uninstallPlugin } from './plugin.js';
1
+ import { findPluginRegistration, listPluginNames, unregisterPlugin } from '@mearl/client/plugin';
2
+ import { resolveAliSkillsInvocation } from './aliSkills.js';
3
+ import { installPlugins } from './plugin.js';
3
4
  import { findExecutable, type CommandRunner } from './process.js';
4
5
  export declare const ALIBABA_NPM_REGISTRY = "https://registry.anpm.alibaba-inc.com/";
5
6
  export declare const ALIBABA_INTERNAL_PLUGIN_NAMES: string[];
6
7
  export interface InternalPluginOptions {
7
8
  env?: NodeJS.ProcessEnv;
9
+ includeMearlSkill?: boolean;
10
+ mearlSkillOperation?: 'install' | 'update';
8
11
  runCommand?: CommandRunner;
9
12
  writeLine?: (line: string) => void;
10
13
  }
@@ -12,8 +15,10 @@ export interface InternalPluginDependencies {
12
15
  findExecutable?: typeof findExecutable;
13
16
  findPluginRegistration?: typeof findPluginRegistration;
14
17
  installPlugins?: typeof installPlugins;
18
+ listPluginNames?: typeof listPluginNames;
15
19
  probeAlibabaRegistry?: (options: InternalPluginOptions) => Promise<boolean>;
16
- uninstallPlugin?: typeof uninstallPlugin;
20
+ resolveAliSkillsInvocation?: typeof resolveAliSkillsInvocation;
21
+ unregisterPlugin?: typeof unregisterPlugin;
17
22
  }
18
23
  export declare function canAccessAlibabaNpmRegistry(options?: InternalPluginOptions, dependencies?: Pick<InternalPluginDependencies, 'findExecutable'>): Promise<boolean>;
19
24
  /** Uses the internal registry only as an intranet probe; Ali Skills installs the plugins. */
@@ -1,6 +1,7 @@
1
1
  import { dirname } from 'node:path';
2
- import { findOfficialPlugin, findPluginRegistration, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, } from '@mearl/client/plugin';
3
- import { installPlugins, uninstallPlugin } from './plugin.js';
2
+ import { findOfficialPlugin, findPluginRegistration, listPluginNames, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, unregisterPlugin, } from '@mearl/client/plugin';
3
+ import { resolveAliSkillsInvocation } from './aliSkills.js';
4
+ import { installPlugins } from './plugin.js';
4
5
  import { findExecutable, prependCommandPath, runCommand } from './process.js';
5
6
  export const ALIBABA_NPM_REGISTRY = 'https://registry.anpm.alibaba-inc.com/';
6
7
  export const ALIBABA_INTERNAL_PLUGIN_NAMES = listOfficialPluginNames('alibaba');
@@ -35,12 +36,15 @@ export async function canAccessAlibabaNpmRegistry(options = {}, dependencies = {
35
36
  export async function installAlibabaInternalPlugins(options = {}, dependencies = {}) {
36
37
  const writeLine = options.writeLine ?? (line => console.error(line));
37
38
  const registrationFor = dependencies.findPluginRegistration ?? findPluginRegistration;
39
+ const missingRegistrations = new Set();
38
40
  const candidates = ALIBABA_INTERNAL_PLUGIN_NAMES.filter(name => {
39
41
  const registration = registrationFor(name);
40
42
  if (registration && registration.managedBy !== OFFICIAL_PLUGIN_MANAGER) {
41
43
  writeLine(`${name} plugin: existing user registration kept.`);
42
44
  return false;
43
45
  }
46
+ if (!registration)
47
+ missingRegistrations.add(name);
44
48
  return true;
45
49
  });
46
50
  if (candidates.length === 0)
@@ -48,50 +52,145 @@ export async function installAlibabaInternalPlugins(options = {}, dependencies =
48
52
  const probe = dependencies.probeAlibabaRegistry ?? (input => canAccessAlibabaNpmRegistry(input));
49
53
  if (!(await probe(options)))
50
54
  return 'skipped';
51
- let installed = false;
52
- let failed = false;
53
- for (const name of candidates) {
54
- const definition = findOfficialPlugin(name);
55
- try {
56
- const code = await (dependencies.installPlugins ?? installPlugins)([...definition.installArgs], options.runCommand, { registrationManagedBy: OFFICIAL_PLUGIN_MANAGER });
57
- if (code !== 0) {
58
- failed = true;
59
- writeLine(`${name} plugin: install failed (exit ${code}).`);
60
- continue;
55
+ if (options.mearlSkillOperation === 'update') {
56
+ const registeredCandidates = candidates.filter(name => !missingRegistrations.has(name));
57
+ const missingCandidates = candidates.filter(name => missingRegistrations.has(name));
58
+ const updateSkillNames = [
59
+ ...(options.includeMearlSkill ? ['mearl'] : []),
60
+ ...registeredCandidates.map(name => findOfficialPlugin(name).skill),
61
+ ];
62
+ if (updateSkillNames.length > 0) {
63
+ const invocation = (dependencies.resolveAliSkillsInvocation ?? resolveAliSkillsInvocation)({
64
+ env: options.env,
65
+ });
66
+ if (!invocation) {
67
+ writeLine('Internal skill batch: ali-skills and npx are both unavailable; update failed.');
68
+ return 'failed';
69
+ }
70
+ try {
71
+ const updated = await (options.runCommand ?? runCommand)(invocation.command, [...invocation.argsPrefix, 'update', ...updateSkillNames, '--global', '--yes'], { env: invocation.env, tee: true });
72
+ if (updated.code !== 0) {
73
+ writeLine(`Internal skill batch: update failed (exit ${updated.code}).`);
74
+ return 'failed';
75
+ }
76
+ if (options.includeMearlSkill)
77
+ writeLine('Skill: updated.');
78
+ for (const name of registeredCandidates)
79
+ writeLine(`${name} plugin: updated.`);
80
+ }
81
+ catch (error) {
82
+ writeLine(`Internal skill batch: ${error instanceof Error ? error.message : String(error)}; update failed.`);
83
+ return 'failed';
84
+ }
85
+ }
86
+ if (missingCandidates.length > 0) {
87
+ const repairArgs = [
88
+ 'trip/mearl',
89
+ '--skill',
90
+ ...missingCandidates.map(name => findOfficialPlugin(name).skill),
91
+ '--yes',
92
+ ];
93
+ try {
94
+ const code = await (dependencies.installPlugins ?? installPlugins)(repairArgs, options.runCommand, { registrationManagedBy: OFFICIAL_PLUGIN_MANAGER });
95
+ if (code !== 0) {
96
+ writeLine(`Internal skill batch: registration repair failed (exit ${code}).`);
97
+ return 'failed';
98
+ }
99
+ for (const name of missingCandidates) {
100
+ writeLine(`${name} plugin: installed and registered.`);
101
+ }
102
+ }
103
+ catch (error) {
104
+ writeLine(`Internal skill batch: ${error instanceof Error ? error.message : String(error)}; registration repair failed.`);
105
+ return 'failed';
61
106
  }
62
- installed = true;
63
- writeLine(`${name} plugin: installed and registered.`);
64
107
  }
65
- catch (error) {
66
- failed = true;
67
- writeLine(`${name} plugin: ${error instanceof Error ? error.message : String(error)}; skipped.`);
108
+ return 'installed';
109
+ }
110
+ const skillNames = [
111
+ ...(options.includeMearlSkill ? ['mearl'] : []),
112
+ ...candidates.map(name => findOfficialPlugin(name).skill),
113
+ ];
114
+ const args = ['trip/mearl', '--skill', ...skillNames, '--yes'];
115
+ try {
116
+ const code = await (dependencies.installPlugins ?? installPlugins)(args, options.runCommand, {
117
+ registrationManagedBy: OFFICIAL_PLUGIN_MANAGER,
118
+ });
119
+ if (code !== 0) {
120
+ writeLine(`Internal skill batch: install failed (exit ${code}).`);
121
+ return 'failed';
68
122
  }
123
+ if (options.includeMearlSkill)
124
+ writeLine('Skill: installed.');
125
+ for (const name of candidates)
126
+ writeLine(`${name} plugin: installed and registered.`);
127
+ return 'installed';
128
+ }
129
+ catch (error) {
130
+ writeLine(`Internal skill batch: ${error instanceof Error ? error.message : String(error)}; install failed.`);
131
+ return 'failed';
69
132
  }
70
- return failed ? 'failed' : installed ? 'installed' : 'skipped';
71
133
  }
72
134
  export async function uninstallAlibabaInternalPlugins(options = {}, dependencies = {}) {
73
135
  const writeLine = options.writeLine ?? (line => console.error(line));
74
136
  const registrationFor = dependencies.findPluginRegistration ?? findPluginRegistration;
75
- let uninstalled = false;
76
- let failed = false;
77
- for (const name of [...ALIBABA_INTERNAL_PLUGIN_NAMES].reverse()) {
137
+ const removedSkillDirectories = new Set();
138
+ const candidates = ALIBABA_INTERNAL_PLUGIN_NAMES.filter(name => {
139
+ const registration = registrationFor(name);
140
+ if (!registration)
141
+ return false;
142
+ if (registration.managedBy !== OFFICIAL_PLUGIN_MANAGER) {
143
+ writeLine(`${name} plugin: existing user registration kept.`);
144
+ return false;
145
+ }
146
+ removedSkillDirectories.add(dirname(dirname(registration.entry)));
147
+ return true;
148
+ });
149
+ const skillNames = [
150
+ ...(options.includeMearlSkill ? ['mearl'] : []),
151
+ ...candidates.map(name => findOfficialPlugin(name).skill),
152
+ ];
153
+ if (skillNames.length === 0)
154
+ return 'skipped';
155
+ const invocation = (dependencies.resolveAliSkillsInvocation ?? resolveAliSkillsInvocation)({
156
+ env: options.env,
157
+ });
158
+ if (!invocation) {
159
+ writeLine('Internal skill batch: ali-skills and npx are both unavailable; uninstall failed.');
160
+ return 'failed';
161
+ }
162
+ const runner = options.runCommand ?? runCommand;
163
+ let removed;
164
+ try {
165
+ removed = await runner(invocation.command, [...invocation.argsPrefix, 'remove', ...skillNames, '--global', '--yes'], { env: invocation.env, tee: true });
166
+ }
167
+ catch (error) {
168
+ writeLine(`Internal skill batch: ${error instanceof Error ? error.message : String(error)}; uninstall failed.`);
169
+ return 'failed';
170
+ }
171
+ if (removed.code !== 0) {
172
+ writeLine(`Internal skill batch: uninstall failed (exit ${removed.code}).`);
173
+ return 'failed';
174
+ }
175
+ const unregister = dependencies.unregisterPlugin ?? unregisterPlugin;
176
+ const registrationsToRemove = new Set(candidates);
177
+ for (const name of (dependencies.listPluginNames ?? listPluginNames)()) {
78
178
  try {
79
179
  const registration = registrationFor(name);
80
- if (registration?.managedBy !== OFFICIAL_PLUGIN_MANAGER) {
81
- if (registration)
82
- writeLine(`${name} plugin: existing user registration kept.`);
83
- continue;
180
+ if (registration && removedSkillDirectories.has(dirname(dirname(registration.entry)))) {
181
+ registrationsToRemove.add(name);
84
182
  }
85
- const result = await (dependencies.uninstallPlugin ?? uninstallPlugin)(name, options.runCommand);
86
- if (!result.removed)
87
- continue;
88
- uninstalled = true;
89
- writeLine(`${name} plugin: uninstalled.`);
90
183
  }
91
- catch (error) {
92
- failed = true;
93
- writeLine(`${name} plugin: ${error instanceof Error ? error.message : String(error)}; uninstall failed.`);
184
+ catch {
185
+ // 无法读取的注册不影响已确认 Skill 的卸载和其他有效注册清理。
94
186
  }
95
187
  }
96
- return failed ? 'failed' : uninstalled ? 'uninstalled' : 'skipped';
188
+ for (const name of [...registrationsToRemove].sort())
189
+ unregister(name);
190
+ for (const name of candidates) {
191
+ writeLine(`${name} plugin: uninstalled.`);
192
+ }
193
+ if (options.includeMearlSkill)
194
+ writeLine('Skill: uninstalled.');
195
+ return 'uninstalled';
97
196
  }
package/dist/lifecycle.js CHANGED
@@ -277,16 +277,61 @@ export async function runMearlLifecycle(options, dependencies = {}) {
277
277
  packagesOk = false;
278
278
  }
279
279
  }
280
- let skillOk = true;
281
280
  const selectedUsesSkill = selected.some(profile => MEARL_PROFILES[profile].usesSkill);
282
- if (packagesOk && selectedUsesSkill && options.skipSkill !== true) {
281
+ const remainingSkillProfiles = ['local', 'cloud'].filter(profile => detected.has(profile) && !selected.includes(profile));
282
+ let runStandaloneSkill = true;
283
+ let skillOk = true;
284
+ if (packagesOk &&
285
+ selectedUsesSkill &&
286
+ selectedUsesClient &&
287
+ options.skipSkill !== true &&
288
+ ((options.operation === 'uninstall' && remainingSkillProfiles.length === 0) ||
289
+ (options.operation !== 'uninstall' && options.targetLine === undefined))) {
290
+ try {
291
+ if (options.operation === 'uninstall') {
292
+ const result = await (dependencies.uninstallInternalPlugins ?? uninstallAlibabaInternalPlugins)({
293
+ env,
294
+ includeMearlSkill: true,
295
+ runCommand: runner,
296
+ writeLine,
297
+ });
298
+ if (result === 'uninstalled')
299
+ runStandaloneSkill = false;
300
+ else if (result === 'failed') {
301
+ runStandaloneSkill = false;
302
+ skillOk = false;
303
+ }
304
+ }
305
+ else {
306
+ const result = await (dependencies.installInternalPlugins ?? installAlibabaInternalPlugins)({
307
+ env,
308
+ includeMearlSkill: true,
309
+ mearlSkillOperation: options.operation,
310
+ runCommand: runner,
311
+ writeLine,
312
+ });
313
+ if (result === 'installed')
314
+ runStandaloneSkill = false;
315
+ else if (result === 'failed') {
316
+ runStandaloneSkill = false;
317
+ skillOk = false;
318
+ }
319
+ }
320
+ }
321
+ catch (error) {
322
+ writeLine(`Internal skill batch: ${error instanceof Error ? error.message : String(error)}; failed.`);
323
+ runStandaloneSkill = false;
324
+ skillOk = false;
325
+ }
326
+ }
327
+ if (packagesOk && skillOk && selectedUsesSkill && options.skipSkill !== true) {
283
328
  try {
284
329
  if (options.operation === 'uninstall') {
285
- const remainingSkillProfiles = ['local', 'cloud'].filter(profile => detected.has(profile) && !selected.includes(profile));
286
330
  if (remainingSkillProfiles.length > 0) {
287
331
  writeLine(`Skill: kept for ${remainingSkillProfiles.join(', ')}.`);
332
+ writeLine(`Internal plugins: kept for ${remainingSkillProfiles.join(', ')}.`);
288
333
  }
289
- else {
334
+ else if (runStandaloneSkill) {
290
335
  skillOk = await runSkill('uninstall', {
291
336
  env,
292
337
  yes: options.yes,
@@ -295,7 +340,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
295
340
  });
296
341
  }
297
342
  }
298
- else {
343
+ else if (runStandaloneSkill) {
299
344
  skillOk = await runSkill(options.operation, {
300
345
  env,
301
346
  yes: options.yes,
@@ -309,41 +354,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
309
354
  skillOk = false;
310
355
  }
311
356
  }
312
- let internalPluginsOk = true;
313
- if (packagesOk && skillOk && selectedUsesClient && options.skipSkill !== true) {
314
- if (options.operation === 'uninstall') {
315
- if (remainingClientProfiles.length > 0) {
316
- writeLine(`Internal plugins: kept for ${remainingClientProfiles.join(', ')}.`);
317
- }
318
- else {
319
- try {
320
- internalPluginsOk =
321
- (await (dependencies.uninstallInternalPlugins ?? uninstallAlibabaInternalPlugins)({
322
- env,
323
- runCommand: runner,
324
- writeLine,
325
- })) !== 'failed';
326
- }
327
- catch (error) {
328
- writeLine(`Internal plugins: ${error instanceof Error ? error.message : String(error)}; uninstall failed.`);
329
- internalPluginsOk = false;
330
- }
331
- }
332
- }
333
- else if (options.targetLine === undefined) {
334
- try {
335
- await (dependencies.installInternalPlugins ?? installAlibabaInternalPlugins)({
336
- env,
337
- runCommand: runner,
338
- writeLine,
339
- });
340
- }
341
- catch (error) {
342
- writeLine(`Internal plugins: ${error instanceof Error ? error.message : String(error)}; skipped.`);
343
- }
344
- }
345
- }
346
- if (packagesOk && skillOk && internalPluginsOk) {
357
+ if (packagesOk && skillOk) {
347
358
  writeLine('Mearl setup complete.');
348
359
  return true;
349
360
  }
package/dist/plugin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, rmdirSync, statSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
- import { assertPluginName, findOfficialPlugin, findPluginEntry, findPluginRegistration, listPluginNames, loadPlugin, OFFICIAL_PLUGIN_MANAGER, registerPlugin, resolvePluginEntry, unregisterPlugin, } from '@mearl/client/plugin';
4
+ import { assertPluginName, findPluginDirectoryEntry, findOfficialPlugin, findPluginEntry, findPluginRegistration, listPluginNames, loadPlugin, OFFICIAL_PLUGIN_MANAGER, registerPlugin, resolvePluginEntry, unregisterPlugin, } from '@mearl/client/plugin';
5
5
  import { ensurePluginRuntime } from './pluginRuntime.js';
6
6
  import { resolveAliSkillsInvocation, stripAnsi } from './aliSkills.js';
7
7
  import { findExecutable, prependNodePath, runCommand } from './process.js';
@@ -178,8 +178,8 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
178
178
  for (const skillPath of paths) {
179
179
  if (!statSync(join(skillPath, 'SKILL.md')).isFile())
180
180
  throw new Error(`安装结果缺少 SKILL.md: ${skillPath}`);
181
- const entry = join(skillPath, 'scripts', 'main.mjs');
182
- if (!existsSync(entry))
181
+ const entry = findPluginDirectoryEntry(skillPath);
182
+ if (!entry)
183
183
  continue;
184
184
  const plugin = await loadPlugin(entry, registrationContext);
185
185
  const name = plugin.name ?? basename(skillPath);
@@ -189,7 +189,7 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
189
189
  plugins.set(name, { entry, description: plugin.description });
190
190
  }
191
191
  if (plugins.size === 0) {
192
- console.error('未找到 scripts/main.mjs 插件入口;自定义入口可用 setup plugin <name> <path> 注册。');
192
+ console.error('未找到 scripts/main.mjs 或 scripts/main.py 插件入口;自定义入口可用 setup plugin <name> <path> 注册。');
193
193
  return 0;
194
194
  }
195
195
  await ensurePluginRuntime(runner);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/setup",
3
- "version": "2.15.3",
3
+ "version": "2.16.1",
4
4
  "description": "One-shot environment setup and configuration for Mearl",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -61,8 +61,8 @@
61
61
  "commander": "^15.0.0",
62
62
  "extract-zip": "^2.0.1",
63
63
  "ws": "^8.18.3",
64
- "@mearl/client": "2.15.3",
65
- "@mearl/provider": "2.15.3"
64
+ "@mearl/client": "2.16.1",
65
+ "@mearl/provider": "2.16.1"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node": "^24.9.1",