@maz-ui/node 5.2.0 → 5.3.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.
@@ -0,0 +1,635 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, rmSync, readdirSync } from 'node:fs';
3
+ import { join, dirname, basename, relative, sep, posix } from 'node:path';
4
+ import { parse, applyEdits, modify } from 'jsonc-parser';
5
+
6
+ const AGENT_TARGETS = [
7
+ "claude",
8
+ "agents-md",
9
+ "cursor",
10
+ "vscode"
11
+ ];
12
+ const MANAGED_MARKER = "Managed by maz-ui";
13
+ const AGENTS_MD_BEGIN = "<!-- BEGIN:maz-ui -->";
14
+ const AGENTS_MD_END = "<!-- END:maz-ui -->";
15
+ const MCP_SERVER_NAME = "maz-ui";
16
+ const DEFAULT_MCP_SERVER = {
17
+ command: "npx",
18
+ args: ["-y", "@maz-ui/mcp@latest"]
19
+ };
20
+ const MAZ_UI_AGENTS_TOOL = {
21
+ name: "maz-ui",
22
+ mcpPackage: "@maz-ui/mcp",
23
+ mcpBin: "maz-ui-mcp",
24
+ generatedBy: "`npx @maz-ui/mcp init` (or the `mazUi.agents` Nuxt option)",
25
+ versionNote: (version) => `> maz-ui ${version} is installed: these rules and the docs in \`node_modules/maz-ui/docs\` match this version.`,
26
+ packages: [
27
+ "maz-ui",
28
+ "@maz-ui/nuxt",
29
+ "@maz-ui/themes",
30
+ "@maz-ui/icons",
31
+ "@maz-ui/utils",
32
+ "@maz-ui/translations"
33
+ ],
34
+ ruleGlobs: ["**/*.{vue,ts,tsx,css,scss}"]
35
+ };
36
+ function escapeRegExp(value) {
37
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
38
+ }
39
+ const toolSetups = /* @__PURE__ */ new WeakMap();
40
+ function resolveTool(tool) {
41
+ let setup = toolSetups.get(tool);
42
+ if (!setup) {
43
+ const marker = tool.marker ?? `Managed by ${tool.name}`;
44
+ setup = {
45
+ tool,
46
+ marker,
47
+ agentsMdBegin: `<!-- BEGIN:${tool.name} -->`,
48
+ agentsMdEnd: `<!-- END:${tool.name} -->`,
49
+ ruleFile: `.claude/rules/${tool.name}.md`,
50
+ managedHeader: new RegExp(`<!-- ${escapeRegExp(marker)}[^\\n]*?-->`),
51
+ managedVersion: new RegExp(`${escapeRegExp(marker)} (\\d[^\\s:]*)`)
52
+ };
53
+ toolSetups.set(tool, setup);
54
+ }
55
+ return setup;
56
+ }
57
+ function readPackageJson(rootDir) {
58
+ try {
59
+ return JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8"));
60
+ } catch {
61
+ return void 0;
62
+ }
63
+ }
64
+ function installsMcpPackage(rootDir, tool = MAZ_UI_AGENTS_TOOL) {
65
+ const pkg = readPackageJson(rootDir);
66
+ return ["dependencies", "devDependencies", "optionalDependencies"].some((field) => pkg?.[field]?.[tool.mcpPackage] !== void 0);
67
+ }
68
+ function detectPackageManager(rootDir) {
69
+ const declared = String(readPackageJson(rootDir)?.packageManager ?? "").split("@")[0];
70
+ if (["pnpm", "yarn", "bun", "npm"].includes(declared))
71
+ return declared;
72
+ const lockfiles = [
73
+ ["pnpm-lock.yaml", "pnpm"],
74
+ ["yarn.lock", "yarn"],
75
+ ["bun.lock", "bun"],
76
+ ["bun.lockb", "bun"]
77
+ ];
78
+ return lockfiles.find(([file]) => existsSync(join(rootDir, file)))?.[1] ?? "npm";
79
+ }
80
+ function localMcpCommands(tool) {
81
+ const bin = [
82
+ tool.mcpBin,
83
+ ...tool.mcpArgs ?? []
84
+ ];
85
+ return {
86
+ pnpm: {
87
+ command: "pnpm",
88
+ args: ["exec", ...bin]
89
+ },
90
+ yarn: {
91
+ command: "yarn",
92
+ args: ["run", ...bin]
93
+ },
94
+ bun: {
95
+ command: "bun",
96
+ args: ["x", "--no-install", ...bin]
97
+ },
98
+ npm: {
99
+ command: "npx",
100
+ args: ["--no-install", ...bin]
101
+ }
102
+ };
103
+ }
104
+ function mcpServerCommand(rootDir, version = "latest", tool = MAZ_UI_AGENTS_TOOL) {
105
+ return installsMcpPackage(rootDir, tool) ? localMcpCommands(tool)[detectPackageManager(rootDir)] : {
106
+ command: "npx",
107
+ args: [
108
+ "-y",
109
+ `${tool.mcpPackage}@${version}`,
110
+ ...tool.mcpArgs ?? []
111
+ ]
112
+ };
113
+ }
114
+ function pinnedMcpVersion(server, tool) {
115
+ const pinned = new RegExp(`^${escapeRegExp(tool.mcpPackage)}@(\\d\\S*)$`);
116
+ return server.args.map((arg) => pinned.exec(arg)?.[1]).find(Boolean);
117
+ }
118
+ function isGeneratedMcpCommand(server, tool) {
119
+ const command = [
120
+ server.command,
121
+ ...Array.isArray(server.args) ? server.args : []
122
+ ].join(" ");
123
+ const args = (tool.mcpArgs ?? []).map((arg) => ` ${escapeRegExp(arg)}`).join("");
124
+ return new RegExp(`^npx -y ${escapeRegExp(tool.mcpPackage)}(?:@\\S+)?${args}$`).test(command) || Object.values(localMcpCommands(tool)).some((local) => command === [local.command, ...local.args].join(" "));
125
+ }
126
+ const CLAUDE_MEMORY_FILES = ["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
127
+ function withVersion(content, version, tool) {
128
+ if (tool.versionNote === false)
129
+ return content;
130
+ const note = tool.versionNote?.(version) ?? `> ${tool.name} ${version} is installed: these rules match this version.`;
131
+ return content.replace(/^(#{1,2} .+)\n/, (_, title) => `${title}
132
+
133
+ ${note}
134
+ `);
135
+ }
136
+ function checksum(body) {
137
+ return createHash("sha256").update(body.replaceAll(/\s+/g, "")).digest("hex").slice(0, 12);
138
+ }
139
+ function ruleChecksum(frontmatter, body) {
140
+ return checksum(frontmatter.replaceAll(/["']/g, "") + body);
141
+ }
142
+ function managedContent(version, body, setup, frontmatter = "") {
143
+ const recorded = ruleChecksum(frontmatter, body);
144
+ return `${frontmatter}<!-- ${setup.marker} ${version} (sha256:${recorded}): generated by ${setup.tool.generatedBy}. Do not edit: edited rules are no longer updated. Write your own rules elsewhere. -->
145
+
146
+ ${body}`;
147
+ }
148
+ function isEditedManagedContent(content, tool = MAZ_UI_AGENTS_TOOL) {
149
+ const header = content === void 0 ? null : resolveTool(tool).managedHeader.exec(content);
150
+ const recorded = header ? /\(sha256:([\da-f]+)\)/.exec(header[0])?.[1] : void 0;
151
+ if (header === null || recorded === void 0)
152
+ return false;
153
+ const before = content.slice(0, header.index);
154
+ const after = content.slice(header.index + header[0].length);
155
+ return recorded !== checksum(after) && recorded !== ruleChecksum(before, after);
156
+ }
157
+ function detectAgentTargets(rootDir) {
158
+ const has = (path) => existsSync(join(rootDir, path));
159
+ const targets = /* @__PURE__ */ new Set();
160
+ if (has(".claude") || CLAUDE_MEMORY_FILES.some(has) || has(".mcp.json"))
161
+ targets.add("claude");
162
+ if (has(".cursor") || has(".cursorrules"))
163
+ targets.add("cursor");
164
+ if (has(".vscode/mcp.json") || has(".github/copilot-instructions.md") || has(".github/instructions"))
165
+ targets.add("vscode");
166
+ if (has("AGENTS.md") || targets.has("cursor") || targets.has("vscode") || has(".codex") || has(".windsurf") || has(".devin") || has(".windsurfrules") || has("GEMINI.md"))
167
+ targets.add("agents-md");
168
+ return AGENT_TARGETS.filter((target) => targets.has(target));
169
+ }
170
+ function findInstalledPackage(fromDir, name) {
171
+ let directory = fromDir;
172
+ for (; ; ) {
173
+ const candidate = join(directory, "node_modules", name);
174
+ if (existsSync(join(candidate, "package.json")))
175
+ return candidate;
176
+ const parent = dirname(directory);
177
+ if (parent === directory)
178
+ return void 0;
179
+ directory = parent;
180
+ }
181
+ }
182
+ function readPackageVersion(packageDir) {
183
+ const file = join(packageDir, "package.json");
184
+ return existsSync(file) ? JSON.parse(readFileSync(file, "utf8")).version : void 0;
185
+ }
186
+ const DEFAULT_WORKSPACES = ["apps/*", "packages/*"];
187
+ const MAX_GLOBSTAR_DEPTH = 3;
188
+ function readWorkspacePatterns(rootDir) {
189
+ const patterns = readDeclaredWorkspacePatterns(rootDir).filter((pattern) => !pattern.startsWith("!"));
190
+ return patterns.length > 0 ? patterns : DEFAULT_WORKSPACES;
191
+ }
192
+ function readDeclaredWorkspacePatterns(rootDir) {
193
+ const pnpmWorkspace = join(rootDir, "pnpm-workspace.yaml");
194
+ const workspaces = readPackageJson(rootDir)?.workspaces;
195
+ const declared = existsSync(pnpmWorkspace) ? readPnpmWorkspacePackages(readFileSync(pnpmWorkspace, "utf8")) : (Array.isArray(workspaces) ? workspaces : workspaces?.packages) ?? [];
196
+ return declared.map((pattern) => pattern.replace(/^(!?)\.\//, "$1").replace(/\/$/, ""));
197
+ }
198
+ function readPnpmWorkspacePackages(content) {
199
+ const patterns = [];
200
+ let inPackages = false;
201
+ for (const line of content.split(/\r?\n/)) {
202
+ const trimmed = line.trim();
203
+ if (!trimmed || trimmed.startsWith("#"))
204
+ continue;
205
+ if (line === line.trimStart())
206
+ inPackages = trimmed === "packages:";
207
+ else if (inPackages && trimmed.startsWith("-"))
208
+ patterns.push(trimmed.slice(1).split("#")[0].trim().replaceAll(/^["']|["']$/g, ""));
209
+ }
210
+ return patterns;
211
+ }
212
+ function globToRegExp(pattern) {
213
+ const source = pattern.split("/").map((segment) => segment === "**" ? ".*" : segment.replaceAll(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("*", "[^/]*").replaceAll("?", "[^/]")).join("/");
214
+ return new RegExp(`^${source.replaceAll("/.*", "(?:/.*)?")}$`);
215
+ }
216
+ function findWorkspaceDirs(rootDir) {
217
+ const folders = /* @__PURE__ */ new Set();
218
+ for (const pattern of readWorkspacePatterns(rootDir)) {
219
+ const segments = pattern.split("/");
220
+ const firstGlob = segments.findIndex((segment) => /[*?[{]/.test(segment));
221
+ const base = (firstGlob === -1 ? segments : segments.slice(0, firstGlob)).join("/");
222
+ const depth = firstGlob === -1 ? 0 : segments.includes("**") ? MAX_GLOBSTAR_DEPTH : segments.length - firstGlob;
223
+ const matcher = globToRegExp(pattern);
224
+ const visit = (folder, remaining) => {
225
+ if (!existsSync(join(rootDir, folder)))
226
+ return;
227
+ if (folder && folder !== "." && matcher.test(folder))
228
+ folders.add(join(rootDir, folder));
229
+ if (remaining === 0)
230
+ return;
231
+ for (const entry of readdirSync(join(rootDir, folder), { withFileTypes: true })) {
232
+ if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
233
+ visit(folder ? `${folder}/${entry.name}` : entry.name, remaining - 1);
234
+ }
235
+ }
236
+ };
237
+ visit(base, depth);
238
+ }
239
+ return [...folders];
240
+ }
241
+ function findProjectPackage(rootDir, name) {
242
+ return [
243
+ rootDir,
244
+ ...findWorkspaceDirs(rootDir)
245
+ ].map((base) => join(base, "node_modules", name)).find((candidate) => existsSync(join(candidate, "package.json"))) ?? findInstalledPackage(rootDir, name);
246
+ }
247
+ function readAgentDocs(rootDir, docsDir) {
248
+ let directory = docsDir;
249
+ if (!directory) {
250
+ const packageDir = findProjectPackage(rootDir, "maz-ui");
251
+ if (!packageDir)
252
+ throw new Error(`maz-ui is not installed in ${rootDir}: install it first (\`npm install maz-ui\`).`);
253
+ directory = join(packageDir, "docs");
254
+ }
255
+ const manifestFile = join(directory, "manifest.json");
256
+ const claudeRuleFile = join(directory, "agents/claude-rule.md");
257
+ const agentsMdFile = join(directory, "agents/agents-md.md");
258
+ if (!existsSync(manifestFile) || !existsSync(claudeRuleFile) || !existsSync(agentsMdFile)) {
259
+ throw new Error(`The installed maz-ui does not ship the agent docs (${directory}): upgrade maz-ui to the latest version.`);
260
+ }
261
+ const packageVersion = readPackageVersion(dirname(directory));
262
+ return {
263
+ directory,
264
+ version: packageVersion ?? JSON.parse(readFileSync(manifestFile, "utf8")).version,
265
+ claudeRule: readFileSync(claudeRuleFile, "utf8").trim(),
266
+ agentsMdSection: readFileSync(agentsMdFile, "utf8").trim()
267
+ };
268
+ }
269
+ function writeFileAtomic(path, content) {
270
+ const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`);
271
+ mkdirSync(dirname(path), { recursive: true });
272
+ try {
273
+ writeFileSync(temporary, content);
274
+ renameSync(temporary, path);
275
+ } catch (error) {
276
+ rmSync(temporary, { force: true });
277
+ throw error;
278
+ }
279
+ }
280
+ function parseVersion(version) {
281
+ const [core, ...prerelease] = version.replace(/^v/, "").split("+")[0].split("-");
282
+ return {
283
+ core: core.split(".").map((part) => Number.parseInt(part, 10) || 0),
284
+ prerelease: prerelease.join("-").split(".").filter(Boolean)
285
+ };
286
+ }
287
+ function comparePrereleaseIdentifiers(a, b) {
288
+ const isNumeric = (value) => /^\d+$/.test(value);
289
+ if (isNumeric(a) !== isNumeric(b)) {
290
+ return isNumeric(a) ? -1 : 1;
291
+ }
292
+ return isNumeric(a) ? Number(a) - Number(b) : a.localeCompare(b);
293
+ }
294
+ function compareVersions(a, b) {
295
+ const left = parseVersion(a);
296
+ const right = parseVersion(b);
297
+ for (let index = 0; index < 3; index++) {
298
+ const difference = (left.core[index] ?? 0) - (right.core[index] ?? 0);
299
+ if (difference !== 0)
300
+ return difference;
301
+ }
302
+ if (left.prerelease.length === 0 || right.prerelease.length === 0)
303
+ return right.prerelease.length - left.prerelease.length;
304
+ for (let index = 0; index < Math.min(left.prerelease.length, right.prerelease.length); index++) {
305
+ const difference = comparePrereleaseIdentifiers(left.prerelease[index], right.prerelease[index]);
306
+ if (difference !== 0)
307
+ return difference;
308
+ }
309
+ return left.prerelease.length - right.prerelease.length;
310
+ }
311
+ function readManagedVersion(content, tool = MAZ_UI_AGENTS_TOOL) {
312
+ return content ? resolveTool(tool).managedVersion.exec(content)?.[1] : void 0;
313
+ }
314
+ function newerManagedVersion(content, version, tool) {
315
+ const written = readManagedVersion(content, tool);
316
+ return written && version && compareVersions(written, version) > 0 ? written : void 0;
317
+ }
318
+ function newerVersionReason(written, version, tool) {
319
+ return `written by ${tool.name} ${written}, newer than the installed ${version}: left untouched`;
320
+ }
321
+ class ChangeSet {
322
+ constructor(rootDir, dryRun) {
323
+ this.rootDir = rootDir;
324
+ this.dryRun = dryRun;
325
+ }
326
+ changes = [];
327
+ absolute(file) {
328
+ return join(this.rootDir, file);
329
+ }
330
+ read(file) {
331
+ const path = this.absolute(file);
332
+ return existsSync(path) ? readFileSync(path, "utf8") : void 0;
333
+ }
334
+ write(file, content, current, reason) {
335
+ if (current === content) {
336
+ this.changes.push({ file, action: "unchanged" });
337
+ return;
338
+ }
339
+ this.changes.push({
340
+ file,
341
+ action: current === void 0 ? "create" : "update",
342
+ ...reason ? { reason } : {}
343
+ });
344
+ if (!this.dryRun)
345
+ writeFileAtomic(this.absolute(file), content);
346
+ }
347
+ delete(file) {
348
+ this.changes.push({ file, action: "remove" });
349
+ if (!this.dryRun)
350
+ rmSync(this.absolute(file), { force: true });
351
+ }
352
+ skip(file, reason) {
353
+ this.changes.push({ file, action: "skip", reason });
354
+ }
355
+ unchanged(file, reason) {
356
+ this.changes.push({ file, action: "unchanged", reason });
357
+ }
358
+ }
359
+ function isOwned(content, setup) {
360
+ return content === void 0 || content.includes(setup.marker);
361
+ }
362
+ function syncOwnedFile(changes, file, content, version, setup) {
363
+ const { tool } = setup;
364
+ const current = changes.read(file);
365
+ if (!isOwned(current, setup)) {
366
+ changes.skip(file, `file exists and was not generated by ${tool.name}: left untouched`);
367
+ return;
368
+ }
369
+ const newer = content === void 0 ? void 0 : newerManagedVersion(current, version, tool);
370
+ if (newer) {
371
+ changes.skip(file, newerVersionReason(newer, version, tool));
372
+ return;
373
+ }
374
+ if (content !== void 0 && isEditedManagedContent(current, tool)) {
375
+ changes.skip(file, `edited since it was generated: left untouched, delete it to get the rules of ${tool.name} ${version}`);
376
+ return;
377
+ }
378
+ if (content === void 0) {
379
+ if (current !== void 0)
380
+ changes.delete(file);
381
+ return;
382
+ }
383
+ changes.write(file, content, current);
384
+ }
385
+ function syncAgentsMd(changes, section, version, setup) {
386
+ const { tool, agentsMdBegin, agentsMdEnd } = setup;
387
+ const file = "AGENTS.md";
388
+ const current = changes.read(file);
389
+ const begin = current?.indexOf(agentsMdBegin) ?? -1;
390
+ const end = current?.indexOf(agentsMdEnd) ?? -1;
391
+ const hasBlock = begin !== -1 && end > begin;
392
+ if (section === void 0) {
393
+ if (!current || !hasBlock)
394
+ return;
395
+ const remaining = `${current.slice(0, begin)}${current.slice(end + agentsMdEnd.length)}`.replace(/\n{3,}/g, "\n\n").trim();
396
+ if (remaining)
397
+ changes.write(file, `${remaining}
398
+ `, current);
399
+ else
400
+ changes.delete(file);
401
+ return;
402
+ }
403
+ const block = hasBlock ? current?.slice(begin + agentsMdBegin.length, end) : void 0;
404
+ const newer = newerManagedVersion(block, version, tool);
405
+ if (newer) {
406
+ changes.skip(file, newerVersionReason(newer, version, tool));
407
+ return;
408
+ }
409
+ if (isEditedManagedContent(block, tool)) {
410
+ changes.skip(file, `the ${tool.name} section was edited since it was generated: left untouched, delete the section to get the rules of ${tool.name} ${version}`);
411
+ return;
412
+ }
413
+ const wrapped = `${agentsMdBegin}
414
+
415
+ ${section}
416
+
417
+ ${agentsMdEnd}`;
418
+ if (!current) {
419
+ changes.write(file, `${wrapped}
420
+ `, current);
421
+ } else if (hasBlock) {
422
+ changes.write(file, `${current.slice(0, begin)}${wrapped}${current.slice(end + agentsMdEnd.length)}`, current);
423
+ } else {
424
+ changes.write(file, `${current.trimEnd()}
425
+
426
+ ${wrapped}
427
+ `, current);
428
+ }
429
+ }
430
+ const MCP_FILES = {
431
+ claude: { file: ".mcp.json", rootKey: "mcpServers", withType: true },
432
+ cursor: { file: ".cursor/mcp.json", rootKey: "mcpServers", withType: false },
433
+ vscode: { file: ".vscode/mcp.json", rootKey: "servers", withType: true }
434
+ };
435
+ function referencesToolMcp(server, tool) {
436
+ const serialized = JSON.stringify(server);
437
+ const args = isJsonObject(server) && Array.isArray(server.args) ? server.args : [];
438
+ return (serialized.includes(tool.mcpPackage) || serialized.includes(tool.mcpBin)) && (tool.mcpArgs ?? []).every((arg) => args.includes(arg));
439
+ }
440
+ function findLegacyPackage(server, tool) {
441
+ const serialized = JSON.stringify(server);
442
+ return tool.legacyMcpPackages?.find((name) => serialized.includes(name));
443
+ }
444
+ function detectFormatting(content) {
445
+ const indent = /^( +|\t)"/m.exec(content)?.[1] ?? " ";
446
+ return {
447
+ insertSpaces: !indent.includes(" "),
448
+ tabSize: indent.includes(" ") ? 1 : indent.length,
449
+ eol: content.includes("\r\n") ? "\r\n" : "\n"
450
+ };
451
+ }
452
+ function isJsonObject(value) {
453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
454
+ }
455
+ function updateMcpCommand(changes, config, [key, entry], server, tool) {
456
+ const current = entry;
457
+ const label = [server.command, ...server.args].join(" ");
458
+ if (current.command === server.command && JSON.stringify(current.args) === JSON.stringify(server.args)) {
459
+ changes.unchanged(config.file, `${tool.name} MCP server already registered as "${key}"`);
460
+ return;
461
+ }
462
+ if (!isGeneratedMcpCommand(current, tool)) {
463
+ changes.unchanged(config.file, `"${key}" runs a custom command: left untouched`);
464
+ return;
465
+ }
466
+ const pinned = pinnedMcpVersion(current, tool);
467
+ const wanted = pinnedMcpVersion(server, tool);
468
+ const isLocal = !server.args.includes("-y");
469
+ if (pinned && !isLocal && (!wanted || compareVersions(pinned, wanted) >= 0)) {
470
+ changes.unchanged(config.file, `"${key}" runs ${tool.mcpPackage} ${pinned}`);
471
+ return;
472
+ }
473
+ const file = changes.read(config.file);
474
+ const formatting = detectFormatting(file);
475
+ let content = applyEdits(file, modify(file, [config.rootKey, key, "command"], server.command, { formattingOptions: formatting }));
476
+ content = applyEdits(content, modify(content, [config.rootKey, key, "args"], server.args, { formattingOptions: formatting }));
477
+ changes.write(config.file, content, file, `"${key}" now runs ${label}`);
478
+ }
479
+ function syncMcpConfig(changes, config, server, remove, tool) {
480
+ const current = changes.read(config.file);
481
+ const errors = [];
482
+ const parsed = current === void 0 ? {} : parse(current, errors, { allowTrailingComma: true });
483
+ if (errors.length > 0 || !isJsonObject(parsed) || parsed[config.rootKey] !== void 0 && !isJsonObject(parsed[config.rootKey])) {
484
+ changes.skip(config.file, "invalid JSON: left untouched");
485
+ return;
486
+ }
487
+ const servers = parsed[config.rootKey] ?? {};
488
+ const existing = Object.entries(servers).find(([, value2]) => referencesToolMcp(value2, tool));
489
+ const base = current ?? "{}\n";
490
+ const formatting = detectFormatting(base);
491
+ if (remove) {
492
+ if (existing?.[0] === tool.name) {
493
+ const content2 = applyEdits(base, modify(base, [config.rootKey, tool.name], void 0, { formattingOptions: formatting }));
494
+ const remaining = parse(content2);
495
+ const isEmpty = Object.keys(remaining).length === 1 && Object.keys(remaining[config.rootKey]).length === 0;
496
+ if (isEmpty)
497
+ changes.delete(config.file);
498
+ else
499
+ changes.write(config.file, content2, current);
500
+ }
501
+ return;
502
+ }
503
+ if (existing) {
504
+ updateMcpCommand(changes, config, existing, server, tool);
505
+ return;
506
+ }
507
+ const legacy = Object.entries(servers).map(([key, entry]) => [
508
+ key,
509
+ findLegacyPackage(entry, tool)
510
+ ]).find(([, name]) => name !== void 0);
511
+ if (legacy) {
512
+ const [key, name] = legacy;
513
+ const migrated = applyEdits(base, modify(base, [config.rootKey, key], config.withType ? { type: "stdio", ...server } : { ...server }, { formattingOptions: formatting }));
514
+ changes.write(config.file, migrated.endsWith("\n") ? migrated : `${migrated}
515
+ `, current, `"${key}" migrated from ${name} to ${tool.mcpPackage}`);
516
+ return;
517
+ }
518
+ if (servers[tool.name] !== void 0) {
519
+ changes.skip(config.file, `a "${tool.name}" server already exists with another command: left untouched`);
520
+ return;
521
+ }
522
+ const value = config.withType ? { type: "stdio", ...server } : { ...server };
523
+ const content = applyEdits(base, modify(base, [config.rootKey, tool.name], value, { formattingOptions: formatting }));
524
+ changes.write(config.file, content.endsWith("\n") ? content : `${content}
525
+ `, current);
526
+ }
527
+ const DEPENDENCY_FIELDS = [
528
+ "dependencies",
529
+ "devDependencies",
530
+ "peerDependencies",
531
+ "optionalDependencies"
532
+ ];
533
+ function dependsOnAny(folder, packages) {
534
+ const pkg = readPackageJson(folder);
535
+ return DEPENDENCY_FIELDS.some((field) => packages.some((name) => pkg?.[field]?.[name] !== void 0));
536
+ }
537
+ function findWorkspacesUsing(rootDir, packages) {
538
+ const excluded = readDeclaredWorkspacePatterns(rootDir).filter((pattern) => pattern.startsWith("!")).map((pattern) => globToRegExp(`${pattern.slice(1)}/**`));
539
+ const folders = [
540
+ rootDir,
541
+ ...findWorkspaceDirs(rootDir)
542
+ ].map((folder) => relative(rootDir, folder).split(sep).join("/")).filter((folder) => !excluded.some((matcher) => matcher.test(folder)));
543
+ return folders.filter((folder) => dependsOnAny(join(rootDir, folder), packages));
544
+ }
545
+ function isUnscopedGlob(glob) {
546
+ return ["**", "**/*", "*", "/**", "/**/*"].includes(glob.trim());
547
+ }
548
+ function resolveRulePaths(rootDir, tool, option = "auto") {
549
+ if (option === "all")
550
+ return void 0;
551
+ const explicit = Array.isArray(option) ? option.map((glob) => glob.trim()).filter(Boolean) : [];
552
+ const globs = Array.isArray(option) ? explicit : autoRulePaths(rootDir, tool);
553
+ return globs.length === 0 || globs.some(isUnscopedGlob) ? void 0 : [
554
+ ...new Set(globs)
555
+ ];
556
+ }
557
+ const MAX_EXPANDED_PATTERNS = 1e3;
558
+ function expandedPatternCount(glob) {
559
+ const groups = glob.replaceAll(/\\./g, "").matchAll(/\{([^{}]*)\}/g);
560
+ return [...groups].reduce((count, [, alternatives]) => count * alternatives.split(",").length, 1);
561
+ }
562
+ function escapeGlobFolder(folder) {
563
+ return folder.replaceAll(/[[\]{}()*?!]/g, String.raw`\$&`);
564
+ }
565
+ function autoRulePaths(rootDir, tool) {
566
+ if (!tool.packages || tool.packages.length === 0)
567
+ return [];
568
+ const files = tool.ruleGlobs ?? ["**/*"];
569
+ const folders = findWorkspacesUsing(rootDir, tool.packages);
570
+ const globsOf = (scopedFolders) => scopedFolders.flatMap((folder) => files.map((glob) => posix.join(escapeGlobFolder(folder), glob)));
571
+ const scoped = globsOf(folders.includes("") || folders.length === 0 ? [""] : folders);
572
+ return scoped.reduce((total, glob) => total + expandedPatternCount(glob), 0) > MAX_EXPANDED_PATTERNS ? globsOf([""]) : scoped;
573
+ }
574
+ function claudeFrontmatter(globs) {
575
+ if (!globs || globs.length === 0)
576
+ return "";
577
+ const items = globs.map((glob) => [
578
+ " -",
579
+ JSON.stringify(glob)
580
+ ].join(" ")).join("\n");
581
+ return `---
582
+ paths:
583
+ ${items}
584
+ ---
585
+
586
+ `;
587
+ }
588
+ function syncRules(changes, rootDir, targets, docs, setup, paths) {
589
+ const version = docs?.version ?? "";
590
+ const hasClaudeMemory = CLAUDE_MEMORY_FILES.some((file) => existsSync(join(rootDir, file)));
591
+ const claudeReadsAgentsMd = targets.includes("agents-md") && !hasClaudeMemory;
592
+ if (targets.includes("claude")) {
593
+ const rule = docs && !claudeReadsAgentsMd ? `${managedContent(version, withVersion(docs.claudeRule, version, setup.tool), setup, claudeFrontmatter(paths))}
594
+ ` : void 0;
595
+ syncOwnedFile(changes, setup.ruleFile, rule, version, setup);
596
+ }
597
+ if (targets.includes("agents-md")) {
598
+ syncAgentsMd(changes, docs ? managedContent(version, withVersion(docs.agentsMdSection, version, setup.tool), setup) : void 0, version, setup);
599
+ }
600
+ }
601
+ function resolveAgentDocs(options, tool) {
602
+ if (options.agentDocs)
603
+ return options.agentDocs;
604
+ if (tool !== MAZ_UI_AGENTS_TOOL)
605
+ throw new Error(`setupAgents needs the rules of ${tool.name}: pass them with the agentDocs option.`);
606
+ return readAgentDocs(options.rootDir, options.docsDir);
607
+ }
608
+ function setupAgents(options) {
609
+ const { rootDir, rules = true, mcp = true, dryRun = false, remove = false, tool = MAZ_UI_AGENTS_TOOL } = options;
610
+ const setup = resolveTool(tool);
611
+ const targets = options.targets ?? detectAgentTargets(rootDir);
612
+ const docs = remove || !rules ? void 0 : resolveAgentDocs(options, tool);
613
+ const version = docs?.version ?? "";
614
+ const changes = new ChangeSet(rootDir, dryRun);
615
+ const server = options.mcpServer ?? mcpServerCommand(rootDir, options.mcpVersion, tool);
616
+ const claudeRulePaths = docs && targets.includes("claude") ? resolveRulePaths(rootDir, tool, options.paths) : void 0;
617
+ if (rules || remove)
618
+ syncRules(changes, rootDir, targets, docs, setup, claudeRulePaths);
619
+ if (mcp || remove) {
620
+ for (const target of ["claude", "cursor", "vscode"]) {
621
+ if (targets.includes(target))
622
+ syncMcpConfig(changes, MCP_FILES[target], server, remove, tool);
623
+ }
624
+ }
625
+ return {
626
+ targets,
627
+ version,
628
+ changes: changes.changes,
629
+ mcpServer: server,
630
+ localMcpServer: !server.args.includes("-y"),
631
+ claudeRulePaths
632
+ };
633
+ }
634
+
635
+ export { AGENTS_MD_BEGIN as A, DEFAULT_MCP_SERVER as D, MANAGED_MARKER as M, AGENTS_MD_END as a, AGENT_TARGETS as b, MAZ_UI_AGENTS_TOOL as c, MCP_SERVER_NAME as d, compareVersions as e, detectAgentTargets as f, detectPackageManager as g, findInstalledPackage as h, findProjectPackage as i, findWorkspaceDirs as j, findWorkspacesUsing as k, installsMcpPackage as l, isEditedManagedContent as m, mcpServerCommand as n, readManagedVersion as o, readPackageVersion as p, readWorkspacePatterns as q, readAgentDocs as r, resolveRulePaths as s, setupAgents as t, writeFileAtomic as w };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maz-ui/node",
3
3
  "type": "module",
4
- "version": "5.2.0",
4
+ "version": "5.3.0",
5
5
  "description": "Node utilities for Maz-UI library",
6
6
  "author": "Louis Mazel <me@loicmazuel.com>",
7
7
  "license": "MIT",
@@ -29,6 +29,12 @@
29
29
  "types": "./dist/index.d.ts",
30
30
  "import": "./dist/index.mjs",
31
31
  "default": "./dist/index.mjs"
32
+ },
33
+ "./agents": {
34
+ "monorepo:dev": "./src/agents.ts",
35
+ "types": "./dist/agents.d.ts",
36
+ "import": "./dist/agents.mjs",
37
+ "default": "./dist/agents.mjs"
32
38
  }
33
39
  },
34
40
  "main": "./dist/index.mjs",
@@ -39,7 +45,7 @@
39
45
  "dist"
40
46
  ],
41
47
  "engines": {
42
- "node": ">=22.12.0"
48
+ "node": "^22.23.4 || ^24.22.1 || >=26.12.1"
43
49
  },
44
50
  "dependencies": {
45
51
  "colorette": "^2.0.20",
@@ -48,14 +54,14 @@
48
54
  "jsonc-parser": "^3.3.1"
49
55
  },
50
56
  "devDependencies": {
51
- "@types/node": "^22.20.5",
52
- "@vitest/coverage-v8": "^5.0.5",
57
+ "@types/node": "^26.6.6",
58
+ "@vitest/coverage-v8": "^5.1.0",
53
59
  "cross-env": "^10.1.0",
54
- "eslint": "^10.13.0",
60
+ "eslint": "^10.14.0",
55
61
  "tsx": "^4.23.15",
56
62
  "typescript": "^6.0.3",
57
63
  "unbuild": "^3.6.1",
58
- "vitest": "5.0.5"
64
+ "vitest": "5.1.0"
59
65
  },
60
66
  "lint-staged": {
61
67
  "*.{js,jsx,ts,tsx,mjs,mts,cjs,md,yml,json}": [