agentsmesh 0.24.0 → 0.25.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/canonical.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { join, basename, dirname, relative, extname, resolve, posix, win32 } from 'path';
2
- import { access, readdir, readFile, realpath, stat, rm, mkdir, lstat, unlink, writeFile, rename, chmod, mkdtemp, cp } from 'fs/promises';
2
+ import { access, readdir, readFile, rm, mkdir, stat, lstat, unlink, writeFile, rename, chmod, realpath, mkdtemp, cp } from 'fs/promises';
3
3
  import { setTimeout } from 'timers/promises';
4
4
  import { constants, existsSync, realpathSync, statSync } from 'fs';
5
5
  import { stringify, parse } from 'yaml';
@@ -1392,7 +1392,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
1392
1392
  toPath: `${AB_SKILLS}/${entry.name}/SKILL.md`,
1393
1393
  feature: "skills"
1394
1394
  });
1395
- const sourceFiles = await readDirRecursive(sourceSkillDir);
1395
+ const sourceFiles = await readDirRecursiveNoSymlinks(sourceSkillDir);
1396
1396
  for (const sourcePath of sourceFiles) {
1397
1397
  if (sourcePath === sourceSkillFile) continue;
1398
1398
  const relativePath = relative(sourceSkillDir, sourcePath).replace(/\\/g, "/");
@@ -2340,7 +2340,7 @@ function matchesExtension(path, extensions) {
2340
2340
  return extensions.some((extension) => path.endsWith(extension));
2341
2341
  }
2342
2342
  async function importFileDirectory(opts) {
2343
- const files = await readDirRecursive(opts.srcDir);
2343
+ const files = await readDirRecursiveNoSymlinks(opts.srcDir);
2344
2344
  const matchedFiles = files.filter((path) => matchesExtension(path, opts.extensions));
2345
2345
  const results = [];
2346
2346
  for (const srcPath of matchedFiles) {
@@ -6654,7 +6654,7 @@ var init_settings_helpers2 = __esm({
6654
6654
  async function importClaudeSkills(projectRoot, results, normalize) {
6655
6655
  const skillsBaseDir = join(projectRoot, CLAUDE_SKILLS_DIR);
6656
6656
  const destBase = join(projectRoot, CLAUDE_CANONICAL_SKILLS_DIR);
6657
- const allFiles = await readDirRecursive(skillsBaseDir);
6657
+ const allFiles = await readDirRecursiveNoSymlinks(skillsBaseDir);
6658
6658
  const skillMdFiles = allFiles.filter((f) => f.endsWith("SKILL.md"));
6659
6659
  for (const skillMdPath of skillMdFiles) {
6660
6660
  const skillDir = dirname(skillMdPath);
@@ -6673,7 +6673,7 @@ async function importClaudeSkills(projectRoot, results, normalize) {
6673
6673
  `);
6674
6674
  continue;
6675
6675
  }
6676
- const skillFiles = await readDirRecursive(skillDir);
6676
+ const skillFiles = await readDirRecursiveNoSymlinks(skillDir);
6677
6677
  for (const filePath of skillFiles) {
6678
6678
  const fileContent = await readFileSafe(filePath);
6679
6679
  if (fileContent === null) continue;
@@ -7298,7 +7298,7 @@ async function importClineRules(projectRoot, results, normalize) {
7298
7298
  feature: "rules"
7299
7299
  });
7300
7300
  } else {
7301
- const ruleFiles = await readDirRecursive(clineRulesPath);
7301
+ const ruleFiles = await readDirRecursiveNoSymlinks(clineRulesPath);
7302
7302
  const mdFiles = ruleFiles.filter((f) => f.endsWith(".md") && !f.includes("/workflows/")).sort();
7303
7303
  const first = mdFiles[0];
7304
7304
  if (first) {
@@ -7362,27 +7362,40 @@ var init_importer_rules = __esm({
7362
7362
  init_constants8();
7363
7363
  }
7364
7364
  });
7365
+ function toStringRecord2(raw) {
7366
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
7367
+ return Object.fromEntries(
7368
+ Object.entries(raw).filter((entry) => typeof entry[1] === "string")
7369
+ );
7370
+ }
7365
7371
  function mapClineServerToCanonical(raw) {
7366
7372
  if (!raw || typeof raw !== "object") return null;
7367
7373
  const obj = raw;
7368
- const command = typeof obj.command === "string" ? obj.command : "";
7369
- if (!command) return null;
7370
- const type = typeof obj.type === "string" ? obj.type : typeof obj.transportType === "string" ? obj.transportType : "stdio";
7371
- const args = Array.isArray(obj.args) ? obj.args.filter((x) => typeof x === "string") : [];
7372
- const envRaw = obj.env;
7373
- const env = envRaw !== null && typeof envRaw === "object" && !Array.isArray(envRaw) ? Object.fromEntries(
7374
- Object.entries(envRaw).filter(
7375
- (entry) => typeof entry[1] === "string"
7376
- )
7377
- ) : {};
7374
+ const transport = typeof obj.type === "string" ? obj.type : void 0;
7375
+ const transportType = typeof obj.transportType === "string" ? obj.transportType : void 0;
7376
+ const env = toStringRecord2(obj.env);
7378
7377
  const description = typeof obj.description === "string" ? obj.description : void 0;
7379
- return {
7380
- ...description !== void 0 && { description },
7381
- type,
7382
- command,
7383
- args,
7384
- env
7385
- };
7378
+ const command = typeof obj.command === "string" ? obj.command : "";
7379
+ if (command) {
7380
+ const args = Array.isArray(obj.args) ? obj.args.filter((x) => typeof x === "string") : [];
7381
+ return {
7382
+ ...description !== void 0 && { description },
7383
+ type: transport ?? transportType ?? "stdio",
7384
+ command,
7385
+ args,
7386
+ env
7387
+ };
7388
+ }
7389
+ if (typeof obj.url === "string") {
7390
+ return {
7391
+ ...description !== void 0 && { description },
7392
+ type: transport ?? transportType ?? "http",
7393
+ url: obj.url,
7394
+ headers: toStringRecord2(obj.headers),
7395
+ env
7396
+ };
7397
+ }
7398
+ return null;
7386
7399
  }
7387
7400
  async function importClineMcp(projectRoot, results) {
7388
7401
  const candidatePaths = [CLINE_MCP_SETTINGS, CLINE_MCP_SETTINGS_LEGACY].map(
@@ -7456,7 +7469,7 @@ var init_reserved = __esm({
7456
7469
  }
7457
7470
  });
7458
7471
  async function readNativeSkill(skillDir) {
7459
- const allFiles = await readDirRecursive(skillDir).catch(() => []);
7472
+ const allFiles = await readDirRecursiveNoSymlinks(skillDir).catch(() => []);
7460
7473
  const entries = [];
7461
7474
  for (const absPath of allFiles) {
7462
7475
  const relPath = relative(skillDir, absPath).replace(/\\/g, "/");
@@ -7522,7 +7535,7 @@ async function importFlatSkill(skillName, srcPath, content, options) {
7522
7535
  async function findDirectorySkills(skillsDir) {
7523
7536
  const skills = /* @__PURE__ */ new Map();
7524
7537
  try {
7525
- const allFiles = await readDirRecursive(skillsDir);
7538
+ const allFiles = await readDirRecursiveNoSymlinks(skillsDir);
7526
7539
  const skillMdFiles = allFiles.filter((f) => basename(f) === "SKILL.md");
7527
7540
  for (const skillMdPath of skillMdFiles) {
7528
7541
  const skillDir = dirname(skillMdPath);
@@ -7653,7 +7666,7 @@ function extractMeta(content, key) {
7653
7666
  return match?.[1]?.trim() ?? null;
7654
7667
  }
7655
7668
  async function loadHooksFromDir(dir, hooks) {
7656
- const files = await readDirRecursive(dir).catch(() => []);
7669
+ const files = await readDirRecursiveNoSymlinks(dir).catch(() => []);
7657
7670
  const shFiles = files.filter((f) => basename(f).endsWith(".sh") && dirname(f) === dir);
7658
7671
  for (const srcPath of shFiles) {
7659
7672
  const content = await readFileSafe(srcPath);
@@ -8388,7 +8401,7 @@ async function importCodexAgentsFromToml(projectRoot, results, normalize) {
8388
8401
  const agentsPath = join(projectRoot, CODEX_AGENTS_DIR);
8389
8402
  const agentsDestDir = join(projectRoot, CODEX_CANONICAL_AGENTS_DIR);
8390
8403
  try {
8391
- const agentFiles = await readDirRecursive(agentsPath);
8404
+ const agentFiles = await readDirRecursiveNoSymlinks(agentsPath);
8392
8405
  const tomlFiles = agentFiles.filter((f) => f.endsWith(".toml"));
8393
8406
  for (const srcPath of tomlFiles) {
8394
8407
  const content = await readFileSafe(srcPath);
@@ -8484,7 +8497,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize) {
8484
8497
  const results = [];
8485
8498
  const codexRulesPath = join(projectRoot, CODEX_RULES_DIR);
8486
8499
  try {
8487
- const ruleFiles = await readDirRecursive(codexRulesPath);
8500
+ const ruleFiles = await readDirRecursiveNoSymlinks(codexRulesPath);
8488
8501
  const mdFiles = ruleFiles.filter((f) => f.endsWith(".md"));
8489
8502
  for (const srcPath of mdFiles) {
8490
8503
  const content = await readFileSafe(srcPath);
@@ -8641,7 +8654,7 @@ async function importCodexRules(projectRoot, results, normalize, normalizeWindsu
8641
8654
  }
8642
8655
  async function importInstructionMirrors(projectRoot, destDir, results, normalize) {
8643
8656
  try {
8644
- const files = await readDirRecursive(join(projectRoot, CODEX_INSTRUCTIONS_DIR));
8657
+ const files = await readDirRecursiveNoSymlinks(join(projectRoot, CODEX_INSTRUCTIONS_DIR));
8645
8658
  const instructionFiles = files.filter((file) => file.endsWith(".md"));
8646
8659
  const instructionsRoot = join(projectRoot, CODEX_INSTRUCTIONS_DIR);
8647
8660
  for (const srcPath of instructionFiles) {
@@ -9087,19 +9100,31 @@ function readMcpServers(content, extension) {
9087
9100
  for (const [name, value] of Object.entries(rawServers)) {
9088
9101
  if (!value || typeof value !== "object" || Array.isArray(value)) continue;
9089
9102
  const server = value;
9090
- if (typeof server.command !== "string") continue;
9091
- servers[name] = {
9092
- type: typeof server.type === "string" ? server.type : "stdio",
9093
- command: server.command,
9094
- args: toStringArray5(server.args),
9095
- env: toStringRecord(server.env),
9096
- description: typeof server.description === "string" ? server.description : void 0
9097
- };
9103
+ const description = typeof server.description === "string" ? server.description : void 0;
9104
+ if (typeof server.command === "string") {
9105
+ servers[name] = {
9106
+ type: typeof server.type === "string" ? server.type : "stdio",
9107
+ command: server.command,
9108
+ args: toStringArray5(server.args),
9109
+ env: toStringRecord(server.env),
9110
+ description
9111
+ };
9112
+ continue;
9113
+ }
9114
+ if (typeof server.url === "string") {
9115
+ servers[name] = {
9116
+ type: typeof server.type === "string" ? server.type : "http",
9117
+ url: server.url,
9118
+ headers: toStringRecord(server.headers),
9119
+ env: toStringRecord(server.env),
9120
+ description
9121
+ };
9122
+ }
9098
9123
  }
9099
9124
  return servers;
9100
9125
  }
9101
9126
  async function importMcp2(projectRoot, results) {
9102
- const files = (await readDirRecursive(join(projectRoot, CONTINUE_MCP_DIR))).filter(
9127
+ const files = (await readDirRecursiveNoSymlinks(join(projectRoot, CONTINUE_MCP_DIR))).filter(
9103
9128
  (file) => [".json", ".yaml", ".yml"].includes(extname(file))
9104
9129
  );
9105
9130
  const merged = {};
@@ -9759,7 +9784,7 @@ function extractWrapperCommand(content) {
9759
9784
  }
9760
9785
  async function importHooks(projectRoot, results) {
9761
9786
  const hooksDir = join(projectRoot, COPILOT_HOOKS_DIR);
9762
- const allFiles = await readDirRecursive(hooksDir).catch(() => []);
9787
+ const allFiles = await readDirRecursiveNoSymlinks(hooksDir).catch(() => []);
9763
9788
  const jsonFiles = allFiles.filter((file) => file.endsWith(".json"));
9764
9789
  const hooks = {};
9765
9790
  for (const srcPath of jsonFiles) {
@@ -9795,7 +9820,7 @@ async function importHooks(projectRoot, results) {
9795
9820
  }
9796
9821
  }
9797
9822
  const legacyDir = join(projectRoot, COPILOT_LEGACY_HOOKS_DIR);
9798
- const legacyFiles = await readDirRecursive(legacyDir).catch(() => []);
9823
+ const legacyFiles = await readDirRecursiveNoSymlinks(legacyDir).catch(() => []);
9799
9824
  const shFiles = legacyFiles.filter(
9800
9825
  (file) => dirname(file) === legacyDir && /^[^-]+-\d+\.sh$/i.test(basename(file))
9801
9826
  );
@@ -9872,7 +9897,7 @@ var init_importer10 = __esm({
9872
9897
  }
9873
9898
  });
9874
9899
  async function skillNamesFromNativeSkillDir(scanRoot) {
9875
- const files = await readDirRecursive(scanRoot);
9900
+ const files = await readDirRecursiveNoSymlinks(scanRoot);
9876
9901
  const names = /* @__PURE__ */ new Set();
9877
9902
  for (const f of files) {
9878
9903
  if (basename(f) === "SKILL.md") {
@@ -9894,7 +9919,7 @@ var init_native_skill_scan = __esm({
9894
9919
  async function inferCopilotPickFromPath(repoRoot, posixPath) {
9895
9920
  const scan = join(repoRoot, ...posixPath.split("/"));
9896
9921
  if (posixPath.startsWith(COPILOT_PROMPTS_DIR)) {
9897
- const files = await readDirRecursive(scan);
9922
+ const files = await readDirRecursiveNoSymlinks(scan);
9898
9923
  const commands = [
9899
9924
  ...new Set(
9900
9925
  files.filter((f) => f.toLowerCase().endsWith(".prompt.md")).map((f) => basename(f, ".prompt.md"))
@@ -9903,7 +9928,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
9903
9928
  return commands.length ? { commands } : {};
9904
9929
  }
9905
9930
  if (posixPath.startsWith(".github/copilot") && !posixPath.includes("copilot-instructions.md")) {
9906
- const files = await readDirRecursive(scan);
9931
+ const files = await readDirRecursiveNoSymlinks(scan);
9907
9932
  const rules = [
9908
9933
  ...new Set(
9909
9934
  files.filter((f) => f.includes(".instructions.md")).map((f) => basename(f).replace(/\.instructions\.md$/i, ""))
@@ -9912,7 +9937,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
9912
9937
  return rules.length ? { rules } : {};
9913
9938
  }
9914
9939
  if (posixPath.startsWith(".github/instructions")) {
9915
- const files = await readDirRecursive(scan);
9940
+ const files = await readDirRecursiveNoSymlinks(scan);
9916
9941
  const names = /* @__PURE__ */ new Set();
9917
9942
  for (const f of files) {
9918
9943
  const b = basename(f);
@@ -9928,7 +9953,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
9928
9953
  return skills.length ? { skills } : {};
9929
9954
  }
9930
9955
  if (posixPath.startsWith(".github/agents")) {
9931
- const files = await readDirRecursive(scan);
9956
+ const files = await readDirRecursiveNoSymlinks(scan);
9932
9957
  const agents = [
9933
9958
  ...new Set(
9934
9959
  files.filter((f) => f.toLowerCase().endsWith(".agent.md")).map((f) => basename(f, ".agent.md"))
@@ -11476,7 +11501,7 @@ async function importSkills3(projectRoot, results, normalize, skillsRelDir = CUR
11476
11501
  for (const [skillName, skillDir] of directorySkills) {
11477
11502
  await importDirectorySkill(skillName, skillDir, options);
11478
11503
  }
11479
- const allFiles = await readDirRecursive(skillsDir).catch(() => []);
11504
+ const allFiles = await readDirRecursiveNoSymlinks(skillsDir).catch(() => []);
11480
11505
  const mdFiles = allFiles.filter((f) => f.endsWith(".md"));
11481
11506
  const handledPaths = new Set(
11482
11507
  Array.from(directorySkills.values()).flatMap(
@@ -11514,11 +11539,11 @@ async function hasGlobalCursorArtifacts(projectRoot) {
11514
11539
  const stat8 = await readFileSafe(p);
11515
11540
  if (stat8 !== null && stat8.trim() !== "") return true;
11516
11541
  }
11517
- const skillFiles = await readDirRecursive(join(projectRoot, CURSOR_SKILLS_DIR));
11542
+ const skillFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_SKILLS_DIR));
11518
11543
  if (skillFiles.some((f) => f.endsWith(".md"))) return true;
11519
- const agentFiles = await readDirRecursive(join(projectRoot, CURSOR_AGENTS_DIR));
11544
+ const agentFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_AGENTS_DIR));
11520
11545
  if (agentFiles.some((f) => f.endsWith(".md"))) return true;
11521
- const commandFiles = await readDirRecursive(join(projectRoot, CURSOR_COMMANDS_DIR));
11546
+ const commandFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_COMMANDS_DIR));
11522
11547
  if (commandFiles.some((f) => f.endsWith(".md"))) return true;
11523
11548
  return false;
11524
11549
  }
@@ -13106,7 +13131,7 @@ async function importGeminiPolicies(projectRoot) {
13106
13131
  const policiesDir = join(projectRoot, GEMINI_POLICIES_DIR);
13107
13132
  let policyFiles;
13108
13133
  try {
13109
- policyFiles = await readDirRecursive(policiesDir);
13134
+ policyFiles = await readDirRecursiveNoSymlinks(policiesDir);
13110
13135
  } catch {
13111
13136
  return results;
13112
13137
  }
@@ -13205,7 +13230,7 @@ var init_importer_strip = __esm({
13205
13230
  });
13206
13231
  async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
13207
13232
  const geminiSkillsPath = join(projectRoot, GEMINI_SKILLS_DIR);
13208
- const skillDirs = await readDirRecursive(geminiSkillsPath);
13233
+ const skillDirs = await readDirRecursiveNoSymlinks(geminiSkillsPath);
13209
13234
  const skillMdFiles = skillDirs.filter((f) => basename(f) === "SKILL.md");
13210
13235
  for (const srcPath of skillMdFiles) {
13211
13236
  const content = await readFileSafe(srcPath);
@@ -13244,7 +13269,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
13244
13269
  toPath: `${GEMINI_CANONICAL_SKILLS_DIR}/${skillName}/SKILL.md`,
13245
13270
  feature: "skills"
13246
13271
  });
13247
- const allSkillFiles = await readDirRecursive(dirname(srcPath));
13272
+ const allSkillFiles = await readDirRecursiveNoSymlinks(dirname(srcPath));
13248
13273
  for (const absPath of allSkillFiles) {
13249
13274
  if (absPath === srcPath) continue;
13250
13275
  const supportContent = await readFileSafe(absPath);
@@ -13263,7 +13288,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
13263
13288
  }
13264
13289
  const geminiAgentsPath = join(projectRoot, GEMINI_AGENTS_DIR);
13265
13290
  try {
13266
- const agentFiles = await readDirRecursive(geminiAgentsPath);
13291
+ const agentFiles = await readDirRecursiveNoSymlinks(geminiAgentsPath);
13267
13292
  const agentMdFiles = agentFiles.filter((f) => f.endsWith(".md"));
13268
13293
  for (const srcPath of agentMdFiles) {
13269
13294
  const content = await readFileSafe(srcPath);
@@ -13396,7 +13421,7 @@ function isUnderGeminiCommands(pathInRepoPosix) {
13396
13421
  async function inferGeminiCommandNamesFromFiles(repoRoot, pathInRepoPosix) {
13397
13422
  const commandsRoot = join(repoRoot, ...GEMINI_COMMANDS_DIR.split("/"));
13398
13423
  const scanDir = join(repoRoot, ...pathInRepoPosix.split("/"));
13399
- const files = await readDirRecursive(scanDir);
13424
+ const files = await readDirRecursiveNoSymlinks(scanDir);
13400
13425
  const names = [];
13401
13426
  for (const f of files) {
13402
13427
  if (!/\.(toml|md)$/i.test(f)) continue;
@@ -15608,7 +15633,7 @@ async function importNonRootRules(projectRoot, results, normalize) {
15608
15633
  }
15609
15634
  async function importHooks2(projectRoot, results) {
15610
15635
  const hooks = {};
15611
- for (const absPath of await readDirRecursive(join(projectRoot, KIRO_HOOKS_DIR))) {
15636
+ for (const absPath of await readDirRecursiveNoSymlinks(join(projectRoot, KIRO_HOOKS_DIR))) {
15612
15637
  if (!absPath.endsWith(".kiro.hook")) continue;
15613
15638
  const parsed = parseKiroHookFile(await readFileSafe(absPath) ?? "");
15614
15639
  if (!parsed) continue;
@@ -15960,7 +15985,7 @@ var init_generator24 = __esm({
15960
15985
  init_constants19();
15961
15986
  }
15962
15987
  });
15963
- function toStringRecord2(value) {
15988
+ function toStringRecord3(value) {
15964
15989
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
15965
15990
  const out2 = {};
15966
15991
  for (const [k, v] of Object.entries(value)) {
@@ -15986,8 +16011,8 @@ function parseOpenCodeMcp(content) {
15986
16011
  out2[name] = {
15987
16012
  type: "url",
15988
16013
  url: entry.url,
15989
- headers: toStringRecord2(entry.headers),
15990
- env: toStringRecord2(entry.environment),
16014
+ headers: toStringRecord3(entry.headers),
16015
+ env: toStringRecord3(entry.environment),
15991
16016
  ...typeof entry.description === "string" ? { description: entry.description } : {}
15992
16017
  };
15993
16018
  continue;
@@ -16001,7 +16026,7 @@ function parseOpenCodeMcp(content) {
16001
16026
  type: "stdio",
16002
16027
  command,
16003
16028
  args,
16004
- env: toStringRecord2(entry.environment),
16029
+ env: toStringRecord3(entry.environment),
16005
16030
  ...typeof entry.description === "string" ? { description: entry.description } : {}
16006
16031
  };
16007
16032
  }
@@ -18823,7 +18848,7 @@ function toStringArray8(value) {
18823
18848
  }
18824
18849
  async function importWorkflows(projectRoot, results, normalize) {
18825
18850
  const workflowsDir = join(projectRoot, WINDSURF_WORKFLOWS_DIR);
18826
- const workflowFiles = await readDirRecursive(workflowsDir);
18851
+ const workflowFiles = await readDirRecursiveNoSymlinks(workflowsDir);
18827
18852
  const workflowMdFiles = workflowFiles.filter((f) => f.endsWith(".md"));
18828
18853
  const destCommandsDir = join(projectRoot, WINDSURF_CANONICAL_COMMANDS_DIR);
18829
18854
  for (const srcPath of workflowMdFiles) {
@@ -19900,6 +19925,100 @@ function redactUrlSecrets(message) {
19900
19925
  }
19901
19926
  );
19902
19927
  }
19928
+ function gitProtocolOptIns() {
19929
+ const on = (v) => v === "1" || v === "true";
19930
+ return {
19931
+ http: on(process.env.AGENTSMESH_ALLOW_INSECURE_GIT),
19932
+ file: on(process.env.AGENTSMESH_ALLOW_LOCAL_GIT)
19933
+ };
19934
+ }
19935
+ function isAllowedGitProtocol(url) {
19936
+ let parsed;
19937
+ try {
19938
+ parsed = new URL(url);
19939
+ } catch {
19940
+ return false;
19941
+ }
19942
+ const { http, file } = gitProtocolOptIns();
19943
+ const allowed = ["https:", "ssh:"];
19944
+ if (http) allowed.push("http:");
19945
+ if (file) allowed.push("file:");
19946
+ return allowed.includes(parsed.protocol);
19947
+ }
19948
+ function gitAllowProtocolEnv() {
19949
+ const { http, file } = gitProtocolOptIns();
19950
+ const protos = ["https", "ssh"];
19951
+ if (http) protos.push("http");
19952
+ if (file) protos.push("file");
19953
+ return protos.join(":");
19954
+ }
19955
+ function assertAllowedGitUrl(url) {
19956
+ if (isAllowedGitProtocol(url)) return;
19957
+ throw new Error(
19958
+ `agentsmesh refuses a git remote with a disallowed transport: "${redactUrlSecrets(url)}". Allowed: https, ssh. Set AGENTSMESH_ALLOW_INSECURE_GIT=1 to permit http, AGENTSMESH_ALLOW_LOCAL_GIT=1 to permit file.`
19959
+ );
19960
+ }
19961
+ function splitSourceRef(source, prefix, defaultRef) {
19962
+ if (!source.startsWith(prefix)) return null;
19963
+ const rest = source.slice(prefix.length).trim();
19964
+ if (!rest) return null;
19965
+ const refIdx = rest.lastIndexOf("@");
19966
+ if (refIdx < 0) return [rest, defaultRef];
19967
+ const slug = rest.slice(0, refIdx).trim();
19968
+ const ref = rest.slice(refIdx + 1).trim();
19969
+ if (!slug || !ref) return null;
19970
+ return [slug, ref];
19971
+ }
19972
+ function parseGithubSource(source) {
19973
+ const parts = splitSourceRef(source, "github:", "latest");
19974
+ if (!parts) return null;
19975
+ const [slug, tag] = parts;
19976
+ const slash = slug.indexOf("/");
19977
+ if (slash < 0) return null;
19978
+ const org = slug.slice(0, slash).trim();
19979
+ const repo = slug.slice(slash + 1).trim();
19980
+ if (!org || !repo || !tag) return null;
19981
+ return { org, repo, tag };
19982
+ }
19983
+ function parseGitlabSource(source) {
19984
+ const parts = splitSourceRef(source, "gitlab:");
19985
+ if (!parts) return null;
19986
+ const [slug, ref] = parts;
19987
+ const slash = slug.lastIndexOf("/");
19988
+ if (slash < 0) return null;
19989
+ const namespace = slug.slice(0, slash).trim();
19990
+ const project31 = slug.slice(slash + 1).trim();
19991
+ if (!namespace || !project31) return null;
19992
+ return {
19993
+ namespace,
19994
+ project: project31,
19995
+ ref,
19996
+ cloneUrl: `https://gitlab.com/${namespace}/${project31}.git`
19997
+ };
19998
+ }
19999
+ function parseGitSource(source) {
20000
+ if (!source.startsWith("git+")) return null;
20001
+ const rest = source.slice(4).trim();
20002
+ if (!rest) return null;
20003
+ const hashIdx = rest.lastIndexOf("#");
20004
+ const url = (hashIdx < 0 ? rest : rest.slice(0, hashIdx)).trim();
20005
+ const ref = hashIdx < 0 ? void 0 : rest.slice(hashIdx + 1).trim();
20006
+ if (!url || hashIdx >= 0 && !ref) return null;
20007
+ if (!isAllowedGitProtocol(url)) return null;
20008
+ return { url, ref };
20009
+ }
20010
+ function parseRemoteSource(source) {
20011
+ const github = parseGithubSource(source);
20012
+ if (github) return { kind: "github", ...github };
20013
+ const gitlab = parseGitlabSource(source);
20014
+ if (gitlab) return { kind: "gitlab", ...gitlab };
20015
+ const git = parseGitSource(source);
20016
+ if (git) return { kind: "git", ...git };
20017
+ return null;
20018
+ }
20019
+ function isSupportedRemoteSource(source) {
20020
+ return parseRemoteSource(source) !== null;
20021
+ }
19903
20022
 
19904
20023
  // src/config/remote/git-remote.ts
19905
20024
  var execFileAsync = promisify(execFile);
@@ -19970,7 +20089,8 @@ function resolveCloneUrl(parsed) {
19970
20089
  }
19971
20090
  async function cloneRepo(cloneUrl, repoDir) {
19972
20091
  ensureNotFlag(cloneUrl, "clone-url");
19973
- await runGit(["clone", cloneUrl, repoDir]);
20092
+ assertAllowedGitUrl(cloneUrl);
20093
+ await runGit(["clone", "-c", "core.symlinks=false", cloneUrl, repoDir]);
19974
20094
  }
19975
20095
  async function checkoutRef(repoDir, ref) {
19976
20096
  ensureNotFlag(ref, "ref");
@@ -19984,7 +20104,10 @@ async function runGit(args, cwd) {
19984
20104
  cwd,
19985
20105
  env: {
19986
20106
  ...process.env,
19987
- GIT_TERMINAL_PROMPT: "0"
20107
+ GIT_TERMINAL_PROMPT: "0",
20108
+ // Constrain transports git may use (incl. mid-clone redirects/insteadOf)
20109
+ // to the same allowlist the clone URL itself passed.
20110
+ GIT_ALLOW_PROTOCOL: gitAllowProtocolEnv()
19988
20111
  }
19989
20112
  });
19990
20113
  return stdout.trim();
@@ -20172,80 +20295,6 @@ async function fetchGithubDefaultBranch(parsed, extendName, options, cacheDir, b
20172
20295
  }
20173
20296
  throw lastError instanceof Error ? lastError : new Error("Failed to clone GitHub default branch");
20174
20297
  }
20175
- function splitSourceRef(source, prefix, defaultRef) {
20176
- if (!source.startsWith(prefix)) return null;
20177
- const rest = source.slice(prefix.length).trim();
20178
- if (!rest) return null;
20179
- const refIdx = rest.lastIndexOf("@");
20180
- if (refIdx < 0) return [rest, defaultRef];
20181
- const slug = rest.slice(0, refIdx).trim();
20182
- const ref = rest.slice(refIdx + 1).trim();
20183
- if (!slug || !ref) return null;
20184
- return [slug, ref];
20185
- }
20186
- function parseGithubSource(source) {
20187
- const parts = splitSourceRef(source, "github:", "latest");
20188
- if (!parts) return null;
20189
- const [slug, tag] = parts;
20190
- const slash = slug.indexOf("/");
20191
- if (slash < 0) return null;
20192
- const org = slug.slice(0, slash).trim();
20193
- const repo = slug.slice(slash + 1).trim();
20194
- if (!org || !repo || !tag) return null;
20195
- return { org, repo, tag };
20196
- }
20197
- function parseGitlabSource(source) {
20198
- const parts = splitSourceRef(source, "gitlab:");
20199
- if (!parts) return null;
20200
- const [slug, ref] = parts;
20201
- const slash = slug.lastIndexOf("/");
20202
- if (slash < 0) return null;
20203
- const namespace = slug.slice(0, slash).trim();
20204
- const project31 = slug.slice(slash + 1).trim();
20205
- if (!namespace || !project31) return null;
20206
- return {
20207
- namespace,
20208
- project: project31,
20209
- ref,
20210
- cloneUrl: `https://gitlab.com/${namespace}/${project31}.git`
20211
- };
20212
- }
20213
- function parseGitSource(source) {
20214
- if (!source.startsWith("git+")) return null;
20215
- const rest = source.slice(4).trim();
20216
- if (!rest) return null;
20217
- const hashIdx = rest.lastIndexOf("#");
20218
- const url = (hashIdx < 0 ? rest : rest.slice(0, hashIdx)).trim();
20219
- const ref = hashIdx < 0 ? void 0 : rest.slice(hashIdx + 1).trim();
20220
- if (!url || hashIdx >= 0 && !ref) return null;
20221
- let parsedUrl;
20222
- try {
20223
- parsedUrl = new URL(url);
20224
- } catch {
20225
- return null;
20226
- }
20227
- const allowInsecure = process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "1" || process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "true";
20228
- const allowLocalGit = process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "1" || process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "true";
20229
- const allowed = ["https:", "ssh:"];
20230
- if (allowInsecure) allowed.push("http:");
20231
- if (allowLocalGit) allowed.push("file:");
20232
- if (!allowed.includes(parsedUrl.protocol)) {
20233
- return null;
20234
- }
20235
- return { url, ref };
20236
- }
20237
- function parseRemoteSource(source) {
20238
- const github = parseGithubSource(source);
20239
- if (github) return { kind: "github", ...github };
20240
- const gitlab = parseGitlabSource(source);
20241
- if (gitlab) return { kind: "gitlab", ...gitlab };
20242
- const git = parseGitSource(source);
20243
- if (git) return { kind: "git", ...git };
20244
- return null;
20245
- }
20246
- function isSupportedRemoteSource(source) {
20247
- return parseRemoteSource(source) !== null;
20248
- }
20249
20298
  async function sweepStaleCache(cacheDir, maxAgeMs) {
20250
20299
  const dir = cacheDir ?? getCacheDir();
20251
20300
  const threshold = Number(process.env.AGENTSMESH_CACHE_MAX_AGE_DAYS ?? 30) * 864e5;
@@ -20479,15 +20528,17 @@ function assertNoBasenameCollisions(feature, paths, stripExt) {
20479
20528
  const idx = Math.max(fwdIdx, bckIdx);
20480
20529
  const base = idx === -1 ? p : p.slice(idx + 1);
20481
20530
  const slug = base.endsWith(stripExt) ? base.slice(0, -stripExt.length) : base;
20482
- const prior = seen.get(slug);
20483
- if (prior !== void 0 && prior !== p) {
20531
+ const key = slug.toLowerCase();
20532
+ const prior = seen.get(key);
20533
+ if (prior !== void 0 && prior.path !== p) {
20534
+ const detail = prior.slug === slug ? `"${slug}"` : `"${prior.slug}" vs "${slug}" (case-insensitive)`;
20484
20535
  throw new CanonicalNameError(
20485
20536
  feature,
20486
20537
  slug,
20487
- `canonical ${feature} files collide on slug "${slug}": ${prior} vs ${p}. Rename one.`
20538
+ `canonical ${feature} files collide on slug ${detail}: ${prior.path} vs ${p}. Rename one.`
20488
20539
  );
20489
20540
  }
20490
- seen.set(slug, p);
20541
+ seen.set(key, { path: p, slug });
20491
20542
  }
20492
20543
  }
20493
20544
 
@@ -20602,7 +20653,7 @@ function toStrArray(v) {
20602
20653
  return [];
20603
20654
  }
20604
20655
  async function parseRules(rulesDir, opts = {}) {
20605
- const files = await readDirRecursive(rulesDir);
20656
+ const files = await readDirRecursiveNoSymlinks(rulesDir);
20606
20657
  const mdFiles = files.filter((f) => {
20607
20658
  if (!f.endsWith(".md")) return false;
20608
20659
  const name = basename(f, ".md");
@@ -20611,6 +20662,7 @@ async function parseRules(rulesDir, opts = {}) {
20611
20662
  warnIfUnrecognizedResourceFormats("rules", rulesDir, files, mdFiles, {
20612
20663
  handledByOtherReader: opts.handledByOtherReader
20613
20664
  });
20665
+ assertNoBasenameCollisions("rule", mdFiles, ".md");
20614
20666
  const rules = [];
20615
20667
  for (const path of mdFiles) {
20616
20668
  const content = await readFileSafe(path);
@@ -20659,7 +20711,7 @@ function toToolsArray(v) {
20659
20711
  return [];
20660
20712
  }
20661
20713
  async function parseCommands(commandsDir, opts = {}) {
20662
- const files = await readDirRecursive(commandsDir);
20714
+ const files = await readDirRecursiveNoSymlinks(commandsDir);
20663
20715
  const mdFiles = files.filter((f) => f.endsWith(".md") && !basename(f).startsWith("_"));
20664
20716
  warnIfUnrecognizedResourceFormats("commands", commandsDir, files, mdFiles, {
20665
20717
  handledByOtherReader: opts.handledByOtherReader
@@ -20719,7 +20771,7 @@ function toHooks(v) {
20719
20771
  return {};
20720
20772
  }
20721
20773
  async function parseAgents(agentsDir, opts = {}) {
20722
- const files = await readDirRecursive(agentsDir);
20774
+ const files = await readDirRecursiveNoSymlinks(agentsDir);
20723
20775
  const mdFiles = files.filter((f) => f.endsWith(".md") && !basename(f).startsWith("_"));
20724
20776
  warnIfUnrecognizedResourceFormats("agents", agentsDir, files, mdFiles, {
20725
20777
  handledByOtherReader: opts.handledByOtherReader
@@ -21303,7 +21355,7 @@ async function importEntities(kind, dir, opts) {
21303
21355
  }
21304
21356
  async function readToolNativeEntities(srcDir, targetId, kind, parseOpts = {}) {
21305
21357
  const specs = directorySpecsFor(getDescriptor(targetId)?.importer, kind);
21306
- const allFiles = await readDirRecursive(srcDir);
21358
+ const allFiles = await readDirRecursiveNoSymlinks(srcDir);
21307
21359
  const nonMdExtensions = /* @__PURE__ */ new Set();
21308
21360
  for (const spec of specs) {
21309
21361
  for (const ext of spec.extensions) {
@@ -21660,7 +21712,7 @@ async function stageSingleFile(sourcePath, destinationDir, acceptMdc) {
21660
21712
  async function stageMarkdownCollection(sourceRoot, destinationDir, acceptMdc) {
21661
21713
  const info = await stat(sourceRoot);
21662
21714
  if (info.isFile()) return stageSingleFile(sourceRoot, destinationDir, acceptMdc);
21663
- const files = (await readDirRecursive(sourceRoot)).filter(
21715
+ const files = (await readDirRecursiveNoSymlinks(sourceRoot)).filter(
21664
21716
  (file) => isAcceptedFile(file, acceptMdc) && !isBoilerplate(basename(file))
21665
21717
  );
21666
21718
  if (files.length === 0) {
@@ -21707,7 +21759,7 @@ async function stagePreferredSkills(sourceRoot, destinationDir, preferredSkillNa
21707
21759
  }
21708
21760
  const wanted = new Set(preferredSkillNames);
21709
21761
  const matches = /* @__PURE__ */ new Map();
21710
- for (const file of await readDirRecursive(sourceRoot)) {
21762
+ for (const file of await readDirRecursiveNoSymlinks(sourceRoot)) {
21711
21763
  if (!file.endsWith("/SKILL.md") && !file.endsWith("\\SKILL.md")) continue;
21712
21764
  const skillDir = dirname(file);
21713
21765
  const skillName = basename(skillDir);
@@ -21755,7 +21807,7 @@ async function stageSkills(sourceRoot, destinationDir, options = {}) {
21755
21807
  return;
21756
21808
  }
21757
21809
  await mkdirp(destinationDir);
21758
- const entries = await readDirRecursive(sourceRoot);
21810
+ const entries = await readDirRecursiveNoSymlinks(sourceRoot);
21759
21811
  const roots = /* @__PURE__ */ new Set();
21760
21812
  for (const file of entries.filter(
21761
21813
  (entry) => entry.endsWith("/SKILL.md") || entry.endsWith("\\SKILL.md")